@take-out/helpers 0.0.32 → 0.0.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/dist/cjs/async/useAsync.native.js +1 -1
  2. package/dist/cjs/async/useAsyncEffect.js.map +1 -1
  3. package/dist/cjs/async/useLazyMount.js.map +1 -1
  4. package/dist/cjs/async/useLazyValue.js.map +1 -1
  5. package/dist/cjs/emitter.cjs +1 -1
  6. package/dist/cjs/emitter.js +1 -1
  7. package/dist/cjs/emitter.js.map +1 -1
  8. package/dist/cjs/emitter.native.js +1 -1
  9. package/dist/cjs/emitter.native.js.map +1 -1
  10. package/dist/cjs/index.cjs +1 -2
  11. package/dist/cjs/index.js +1 -2
  12. package/dist/cjs/index.js.map +1 -1
  13. package/dist/cjs/index.native.js +2 -4
  14. package/dist/cjs/index.native.js.map +1 -1
  15. package/dist/cjs/react/createGlobalContext.js.map +1 -1
  16. package/dist/cjs/storage/createStorage.cjs +83 -0
  17. package/dist/cjs/storage/createStorage.js +81 -0
  18. package/dist/cjs/storage/createStorage.js.map +6 -0
  19. package/dist/cjs/storage/createStorage.native.js +118 -0
  20. package/dist/cjs/storage/createStorage.native.js.map +6 -0
  21. package/dist/cjs/storage/driver.cjs +38 -0
  22. package/dist/cjs/storage/driver.js +33 -0
  23. package/dist/cjs/storage/driver.js.map +6 -0
  24. package/dist/cjs/storage/driver.native.js +47 -0
  25. package/dist/cjs/storage/driver.native.js.map +6 -0
  26. package/dist/cjs/storage/index.cjs +30 -0
  27. package/dist/cjs/storage/index.js +24 -0
  28. package/dist/cjs/storage/index.js.map +6 -0
  29. package/dist/cjs/storage/index.native.js +32 -0
  30. package/dist/cjs/storage/index.native.js.map +6 -0
  31. package/dist/cjs/storage/types.cjs +16 -0
  32. package/dist/cjs/storage/types.js +14 -0
  33. package/dist/cjs/storage/types.js.map +6 -0
  34. package/dist/cjs/storage/types.native.js +15 -0
  35. package/dist/cjs/storage/types.native.js.map +6 -0
  36. package/dist/esm/async/useAsync.native.js +1 -1
  37. package/dist/esm/async/useAsyncEffect.js.map +1 -1
  38. package/dist/esm/async/useAsyncEffect.mjs.map +1 -1
  39. package/dist/esm/async/useAsyncEffect.native.js.map +1 -1
  40. package/dist/esm/async/useLazyMount.js.map +1 -1
  41. package/dist/esm/async/useLazyMount.mjs.map +1 -1
  42. package/dist/esm/async/useLazyMount.native.js.map +1 -1
  43. package/dist/esm/async/useLazyValue.js.map +1 -1
  44. package/dist/esm/async/useLazyValue.mjs.map +1 -1
  45. package/dist/esm/async/useLazyValue.native.js.map +1 -1
  46. package/dist/esm/emitter.js +1 -1
  47. package/dist/esm/emitter.js.map +1 -1
  48. package/dist/esm/emitter.mjs +1 -1
  49. package/dist/esm/emitter.mjs.map +1 -1
  50. package/dist/esm/emitter.native.js +1 -1
  51. package/dist/esm/emitter.native.js.map +1 -1
  52. package/dist/esm/index.js +1 -2
  53. package/dist/esm/index.js.map +1 -1
  54. package/dist/esm/index.mjs +1 -2
  55. package/dist/esm/index.mjs.map +1 -1
  56. package/dist/esm/index.native.js +1 -2
  57. package/dist/esm/index.native.js.map +1 -1
  58. package/dist/esm/react/createGlobalContext.js.map +1 -1
  59. package/dist/esm/react/createGlobalContext.mjs.map +1 -1
  60. package/dist/esm/react/createGlobalContext.native.js.map +1 -1
  61. package/dist/esm/storage/createStorage.js +65 -0
  62. package/dist/esm/storage/createStorage.js.map +6 -0
  63. package/dist/esm/storage/createStorage.mjs +59 -0
  64. package/dist/esm/storage/createStorage.mjs.map +1 -0
  65. package/dist/esm/storage/createStorage.native.js +94 -0
  66. package/dist/esm/storage/createStorage.native.js.map +1 -0
  67. package/dist/esm/storage/driver.js +17 -0
  68. package/dist/esm/storage/driver.js.map +6 -0
  69. package/dist/esm/storage/driver.mjs +14 -0
  70. package/dist/esm/storage/driver.mjs.map +1 -0
  71. package/dist/esm/storage/driver.native.js +22 -0
  72. package/dist/esm/storage/driver.native.js.map +1 -0
  73. package/dist/esm/storage/index.js +9 -0
  74. package/dist/esm/storage/index.js.map +6 -0
  75. package/dist/esm/storage/index.mjs +4 -0
  76. package/dist/esm/storage/index.mjs.map +1 -0
  77. package/dist/esm/storage/index.native.js +4 -0
  78. package/dist/esm/storage/index.native.js.map +1 -0
  79. package/dist/esm/storage/types.js +1 -0
  80. package/dist/esm/storage/types.js.map +6 -0
  81. package/dist/esm/storage/types.mjs +2 -0
  82. package/dist/esm/storage/types.mjs.map +1 -0
  83. package/dist/esm/storage/types.native.js +2 -0
  84. package/dist/esm/storage/types.native.js.map +1 -0
  85. package/package.json +6 -6
  86. package/src/async/asyncContext.ts +2 -2
  87. package/src/async/useAsync.ts +1 -1
  88. package/src/async/useAsyncEffect.ts +3 -2
  89. package/src/async/useLazyMount.ts +3 -1
  90. package/src/async/useLazyValue.ts +1 -0
  91. package/src/emitter.tsx +8 -7
  92. package/src/index.ts +3 -2
  93. package/src/object/object.ts +1 -1
  94. package/src/react/createGlobalContext.ts +1 -0
  95. package/src/storage/createStorage.ts +141 -0
  96. package/src/storage/driver.ts +20 -0
  97. package/src/storage/index.ts +4 -0
  98. package/src/storage/types.ts +6 -0
  99. package/types/async/asyncContext.d.ts.map +1 -1
  100. package/types/async/useAsync.d.ts.map +1 -1
  101. package/types/async/useAsyncEffect.d.ts.map +2 -2
  102. package/types/async/useLazyMount.d.ts +1 -1
  103. package/types/async/useLazyMount.d.ts.map +2 -2
  104. package/types/async/useLazyValue.d.ts.map +2 -2
  105. package/types/emitter.d.ts.map +2 -2
  106. package/types/index.d.ts +2 -2
  107. package/types/index.d.ts.map +2 -2
  108. package/types/object/object.d.ts +1 -1
  109. package/types/object/object.d.ts.map +2 -2
  110. package/types/react/createGlobalContext.d.ts.map +2 -2
  111. package/types/storage/createStorage.d.ts +65 -0
  112. package/types/storage/createStorage.d.ts.map +18 -0
  113. package/types/storage/driver.d.ts +5 -0
  114. package/types/storage/driver.d.ts.map +13 -0
  115. package/types/storage/index.d.ts +6 -0
  116. package/types/storage/index.d.ts.map +11 -0
  117. package/types/storage/types.d.ts +8 -0
  118. package/types/storage/types.d.ts.map +14 -0
  119. package/src/array/fuzzy.ts +0 -39
  120. package/src/browser/localStorage.ts +0 -311
  121. package/types/ensure/catchEnsureErrors.d.ts +0 -3
  122. package/types/ensure/catchEnsureErrors.d.ts.map +0 -13
  123. package/types/ensure/ensureSingleton.d.ts +0 -7
  124. package/types/ensure/ensureSingleton.d.ts.map +0 -14
  125. package/types/files/download.d.ts +0 -4
  126. package/types/files/download.d.ts.map +0 -16
  127. package/types/global/globalContext.d.ts +0 -6
  128. package/types/global/globalContext.d.ts.map +0 -14
@@ -0,0 +1,141 @@
1
+ import { getStorageDriver } from './driver'
2
+
3
+ const namespaces = new Set<string>()
4
+
5
+ /**
6
+ * namespaced storage interface with JSON serialization
7
+ * @template K - key type (string literal union for type-safe keys)
8
+ * @template V - value type (automatically JSON serialized/deserialized)
9
+ */
10
+ export interface Storage<K extends string = string, V = unknown> {
11
+ /** get a JSON-parsed value by key */
12
+ get(key: K): V | undefined
13
+ /** set a value (JSON serialized) */
14
+ set(key: K, value: V): void
15
+ /** remove a key */
16
+ remove(key: K): void
17
+ /** check if key exists */
18
+ has(key: K): boolean
19
+ /** get all keys in this namespace */
20
+ keys(): K[]
21
+ /** remove all keys in this namespace */
22
+ clear(): void
23
+ /** get raw string value (no JSON parsing) - localStorage compatible */
24
+ getItem(key: K): string | null
25
+ /** set raw string value (no JSON serialization) - localStorage compatible */
26
+ setItem(key: K, value: string): void
27
+ }
28
+
29
+ /**
30
+ * create a namespaced storage instance
31
+ * @param namespace - unique prefix for all keys (throws if already used)
32
+ * @returns storage instance with get/set (JSON) and getItem/setItem (raw) methods
33
+ * @example
34
+ * const store = createStorage<'token' | 'user', string>('auth')
35
+ * store.set('token', 'abc123')
36
+ * store.get('token') // 'abc123'
37
+ */
38
+ export function createStorage<K extends string, V>(namespace: string): Storage<K, V> {
39
+ if (namespaces.has(namespace)) {
40
+ throw new Error(`storage namespace already exists: ${namespace}`)
41
+ }
42
+ namespaces.add(namespace)
43
+
44
+ const prefix = `${namespace}:`
45
+ const prefixKey = (key: string) => `${prefix}${key}`
46
+
47
+ return {
48
+ get(key: K): V | undefined {
49
+ const driver = getStorageDriver()
50
+ if (!driver) return undefined
51
+ const raw = driver.getItem(prefixKey(key))
52
+ if (raw == null) return undefined
53
+ try {
54
+ return JSON.parse(raw)
55
+ } catch {
56
+ return undefined
57
+ }
58
+ },
59
+
60
+ set(key: K, value: V): void {
61
+ const driver = getStorageDriver()
62
+ if (!driver) return
63
+ driver.setItem(prefixKey(key), JSON.stringify(value))
64
+ },
65
+
66
+ remove(key: K): void {
67
+ const driver = getStorageDriver()
68
+ if (!driver) return
69
+ driver.removeItem(prefixKey(key))
70
+ },
71
+
72
+ has(key: K): boolean {
73
+ const driver = getStorageDriver()
74
+ if (!driver) return false
75
+ return driver.getItem(prefixKey(key)) != null
76
+ },
77
+
78
+ keys(): K[] {
79
+ const driver = getStorageDriver()
80
+ if (!driver) return []
81
+ return driver
82
+ .getAllKeys()
83
+ .filter((k) => k.startsWith(prefix))
84
+ .map((k) => k.slice(prefix.length) as K)
85
+ },
86
+
87
+ clear(): void {
88
+ const driver = getStorageDriver()
89
+ if (!driver) return
90
+ for (const key of this.keys()) {
91
+ driver.removeItem(prefixKey(key))
92
+ }
93
+ },
94
+
95
+ getItem(key: K): string | null {
96
+ const driver = getStorageDriver()
97
+ if (!driver) return null
98
+ return driver.getItem(prefixKey(key)) ?? null
99
+ },
100
+
101
+ setItem(key: K, value: string): void {
102
+ const driver = getStorageDriver()
103
+ if (!driver) return
104
+ driver.setItem(prefixKey(key), value)
105
+ },
106
+ }
107
+ }
108
+
109
+ /**
110
+ * single-value storage interface
111
+ * @template T - value type (automatically JSON serialized/deserialized)
112
+ */
113
+ export interface StorageValue<T> {
114
+ /** get the stored value */
115
+ get(): T | undefined
116
+ /** set the value */
117
+ set(value: T): void
118
+ /** remove the value */
119
+ remove(): void
120
+ /** check if value exists */
121
+ has(): boolean
122
+ }
123
+
124
+ /**
125
+ * create a single-value storage (wrapper around createStorage)
126
+ * @param key - unique storage key
127
+ * @returns storage value instance
128
+ * @example
129
+ * const token = createStorageValue<string>('auth-token')
130
+ * token.set('abc123')
131
+ * token.get() // 'abc123'
132
+ */
133
+ export function createStorageValue<T>(key: string): StorageValue<T> {
134
+ const storage = createStorage<'value', T>(`_v:${key}`)
135
+ return {
136
+ get: (): T | undefined => storage.get('value'),
137
+ set: (value: T): void => storage.set('value', value),
138
+ remove: (): void => storage.remove('value'),
139
+ has: (): boolean => storage.has('value'),
140
+ }
141
+ }
@@ -0,0 +1,20 @@
1
+ import type { StorageDriver } from './types'
2
+
3
+ let driver: StorageDriver | null = null
4
+
5
+ export function setStorageDriver(d: StorageDriver): void {
6
+ driver = d
7
+ }
8
+
9
+ export function getStorageDriver(): StorageDriver | null {
10
+ if (driver) return driver
11
+ if (typeof localStorage !== 'undefined') {
12
+ return {
13
+ getItem: (key) => localStorage.getItem(key),
14
+ setItem: (key, value) => localStorage.setItem(key, value),
15
+ removeItem: (key) => localStorage.removeItem(key),
16
+ getAllKeys: () => Object.keys(localStorage),
17
+ }
18
+ }
19
+ return null
20
+ }
@@ -0,0 +1,4 @@
1
+ export { createStorage, createStorageValue } from './createStorage'
2
+ export type { Storage, StorageValue } from './createStorage'
3
+ export { getStorageDriver, setStorageDriver } from './driver'
4
+ export type { StorageDriver } from './types'
@@ -0,0 +1,6 @@
1
+ export interface StorageDriver {
2
+ getItem(key: string): string | null | undefined
3
+ setItem(key: string, value: string): void
4
+ removeItem(key: string): void
5
+ getAllKeys(): string[]
6
+ }
@@ -8,7 +8,7 @@
8
8
  "src/async/asyncContext.ts"
9
9
  ],
10
10
  "sourcesContent": [
11
- "interface AsyncContext<T> {\n get(): T | undefined\n run<R>(value: T, fn: () => R | Promise<R>): Promise<R>\n}\n\ninterface NodeAsyncLocalStorage<T> {\n getStore(): T | undefined\n run<R>(store: T, callback: () => R): R\n}\n\ninterface AsyncLocalStorageConstructor {\n new <T>(): NodeAsyncLocalStorage<T>\n}\n\nlet nodeAsyncLocalStorageCache: AsyncLocalStorageConstructor | null = null\n\nasync function getNodeAsyncLocalStorage(): Promise<AsyncLocalStorageConstructor | null> {\n if (!nodeAsyncLocalStorageCache) {\n try {\n const module = await import('node:async_hooks')\n nodeAsyncLocalStorageCache =\n module.AsyncLocalStorage as AsyncLocalStorageConstructor\n } catch {\n return null\n }\n }\n return nodeAsyncLocalStorageCache\n}\n\nexport function createAsyncContext<T>(): AsyncContext<T> {\n if (process.env.VITE_ENVIRONMENT === 'ssr') {\n let storage: NodeAsyncLocalStorage<T> | null = null\n\n getNodeAsyncLocalStorage().then((AsyncLocalStorage) => {\n if (AsyncLocalStorage && !storage) {\n storage = new AsyncLocalStorage<T>()\n }\n })\n\n return {\n get(): T | undefined {\n if (!storage) {\n console.warn(`⚠️ called AsyncContext before load!`)\n return\n }\n\n return storage.getStore()\n },\n\n async run<R>(value: T, fn: () => R | Promise<R>): Promise<R> {\n if (!storage) {\n throw new Error(`⚠️ called AsyncContext before load!`)\n }\n return storage.run(value, fn)\n },\n }\n } else {\n // browser implementation using promise patching\n return createBrowserAsyncContext<T>()\n }\n}\n\nfunction createBrowserAsyncContext<T>(): AsyncContext<T> {\n let currentContext: T | undefined\n const contextStack: (T | undefined)[] = []\n\n return {\n get(): T | undefined {\n return currentContext\n },\n async run<R>(value: T, fn: () => R | Promise<R>): Promise<R> {\n const prevContext = currentContext\n currentContext = value\n contextStack.push(prevContext)\n\n // store original Promise methods\n const OriginalPromise = Promise\n const OriginalThen = OriginalPromise.prototype.then\n const OriginalCatch = OriginalPromise.prototype.catch\n const OriginalFinally = OriginalPromise.prototype.finally\n\n function wrapCallback(\n callback: Function | undefined | null,\n context: T | undefined\n ): Function | undefined | null {\n if (!callback) return callback\n return (...args: any[]) => {\n const prevContext = currentContext\n currentContext = context\n try {\n return callback(...args)\n } finally {\n currentContext = prevContext\n }\n }\n }\n\n // patch Promise methods to capture and restore context\n // biome-ignore lint/suspicious/noThenProperty: intentional patching for context propagation\n OriginalPromise.prototype.then = function (\n this: Promise<any>,\n onFulfilled?: any,\n onRejected?: any\n ): Promise<any> {\n const context = currentContext\n return OriginalThen.call(\n this,\n wrapCallback(onFulfilled, context) as any,\n wrapCallback(onRejected, context) as any\n )\n }\n\n OriginalPromise.prototype.catch = function (\n this: Promise<any>,\n onRejected?: any\n ): Promise<any> {\n const context = currentContext\n return OriginalCatch.call(this, wrapCallback(onRejected, context) as any)\n }\n\n OriginalPromise.prototype.finally = function (\n this: Promise<any>,\n onFinally?: any\n ): Promise<any> {\n const context = currentContext\n return OriginalFinally.call(this, wrapCallback(onFinally, context) as any)\n }\n\n try {\n const result = await fn()\n return result\n } finally {\n // restore original Promise methods\n // biome-ignore lint/suspicious/noThenProperty: restoring original methods\n OriginalPromise.prototype.then = OriginalThen\n OriginalPromise.prototype.catch = OriginalCatch\n OriginalPromise.prototype.finally = OriginalFinally\n\n contextStack.pop()\n currentContext = prevContext\n }\n },\n }\n}\n"
11
+ "interface AsyncContext<T> {\n get(): T | undefined\n run<R>(value: T, fn: () => R | Promise<R>): Promise<R>\n}\n\ninterface NodeAsyncLocalStorage<T> {\n getStore(): T | undefined\n run<R>(store: T, callback: () => R): R\n}\n\ninterface AsyncLocalStorageConstructor {\n new <T>(): NodeAsyncLocalStorage<T>\n}\n\nlet nodeAsyncLocalStorageCache: AsyncLocalStorageConstructor | null = null\n\nasync function getNodeAsyncLocalStorage(): Promise<AsyncLocalStorageConstructor | null> {\n if (!nodeAsyncLocalStorageCache) {\n try {\n const module = await import('node:async_hooks')\n nodeAsyncLocalStorageCache =\n module.AsyncLocalStorage as AsyncLocalStorageConstructor\n } catch {\n return null\n }\n }\n return nodeAsyncLocalStorageCache\n}\n\nexport function createAsyncContext<T>(): AsyncContext<T> {\n if (process.env.VITE_ENVIRONMENT === 'ssr') {\n let storage: NodeAsyncLocalStorage<T> | null = null\n\n getNodeAsyncLocalStorage().then((AsyncLocalStorage) => {\n if (AsyncLocalStorage && !storage) {\n storage = new AsyncLocalStorage<T>()\n }\n })\n\n return {\n get(): T | undefined {\n if (!storage) {\n console.warn(`⚠️ called AsyncContext before load!`)\n return\n }\n\n return storage.getStore()\n },\n\n async run<R>(value: T, fn: () => R | Promise<R>): Promise<R> {\n if (!storage) {\n throw new Error(`⚠️ called AsyncContext before load!`)\n }\n return storage.run(value, fn)\n },\n }\n } else {\n // browser implementation using promise patching\n return createBrowserAsyncContext<T>()\n }\n}\n\nfunction createBrowserAsyncContext<T>(): AsyncContext<T> {\n let currentContext: T | undefined\n const contextStack: (T | undefined)[] = []\n\n return {\n get(): T | undefined {\n return currentContext\n },\n async run<R>(value: T, fn: () => R | Promise<R>): Promise<R> {\n const prevContext = currentContext\n currentContext = value\n contextStack.push(prevContext)\n\n // store original Promise methods\n const OriginalPromise = Promise\n const OriginalThen = OriginalPromise.prototype.then\n const OriginalCatch = OriginalPromise.prototype.catch\n const OriginalFinally = OriginalPromise.prototype.finally\n\n function wrapCallback(\n callback: Function | undefined | null,\n context: T | undefined\n ): Function | undefined | null {\n if (!callback) return callback\n return (...args: any[]) => {\n const prevContext = currentContext\n currentContext = context\n try {\n return callback(...args)\n } finally {\n currentContext = prevContext\n }\n }\n }\n\n // patch Promise methods to capture and restore context\n // eslint-disable-next-line no-then-property -- intentional patching for context propagation\n OriginalPromise.prototype.then = function (\n this: Promise<any>,\n onFulfilled?: any,\n onRejected?: any\n ): Promise<any> {\n const context = currentContext\n return OriginalThen.call(\n this,\n wrapCallback(onFulfilled, context) as any,\n wrapCallback(onRejected, context) as any\n )\n }\n\n OriginalPromise.prototype.catch = function (\n this: Promise<any>,\n onRejected?: any\n ): Promise<any> {\n const context = currentContext\n return OriginalCatch.call(this, wrapCallback(onRejected, context) as any)\n }\n\n OriginalPromise.prototype.finally = function (\n this: Promise<any>,\n onFinally?: any\n ): Promise<any> {\n const context = currentContext\n return OriginalFinally.call(this, wrapCallback(onFinally, context) as any)\n }\n\n try {\n const result = await fn()\n return result\n } finally {\n // restore original Promise methods\n // eslint-disable-next-line no-then-property -- restoring original methods\n OriginalPromise.prototype.then = OriginalThen\n OriginalPromise.prototype.catch = OriginalCatch\n OriginalPromise.prototype.finally = OriginalFinally\n\n contextStack.pop()\n currentContext = prevContext\n }\n },\n }\n}\n"
12
12
  ],
13
13
  "version": 3
14
14
  }
@@ -8,7 +8,7 @@
8
8
  "src/async/useAsync.ts"
9
9
  ],
10
10
  "sourcesContent": [
11
- "import { useState, useEffect } from 'react'\n\nexport function useAsync<T>(\n promiseFn: () => Promise<T>,\n args: any[]\n): [T, 'loading' | 'idle' | 'error', Error | null] {\n const [data, setData] = useState<T | null>(null)\n const [error, setError] = useState<Error | null>(null)\n const [loading, setLoading] = useState<boolean>(true)\n\n useEffect(() => {\n let isMounted = true\n\n const fetchData = async () => {\n try {\n const result = await promiseFn()\n if (isMounted) {\n setData(result)\n setLoading(false)\n }\n } catch (err) {\n if (isMounted) {\n setError(err as Error)\n setLoading(false)\n }\n }\n }\n\n fetchData()\n\n return () => {\n isMounted = false\n }\n // biome-ignore lint/correctness/useExhaustiveDependencies: ignore\n }, args)\n\n return [data as T, error ? 'error' : loading ? 'loading' : 'idle', error]\n}\n"
11
+ "import { useState, useEffect } from 'react'\n\nexport function useAsync<T>(\n promiseFn: () => Promise<T>,\n args: any[]\n): [T, 'loading' | 'idle' | 'error', Error | null] {\n const [data, setData] = useState<T | null>(null)\n const [error, setError] = useState<Error | null>(null)\n const [loading, setLoading] = useState<boolean>(true)\n\n useEffect(() => {\n let isMounted = true\n\n const fetchData = async () => {\n try {\n const result = await promiseFn()\n if (isMounted) {\n setData(result)\n setLoading(false)\n }\n } catch (err) {\n if (isMounted) {\n setError(err as Error)\n setLoading(false)\n }\n }\n }\n\n fetchData()\n\n return () => {\n isMounted = false\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, args)\n\n return [data as T, error ? 'error' : loading ? 'loading' : 'idle', error]\n}\n"
12
12
  ],
13
13
  "version": 3
14
14
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "mappings": "KAMK;KAEA,uBACHA,QAAQ,aACR,GAAG,gBACA,QAAQ;KAER,qBAAqB;CACxB;CACA;CACA;AACD;AAED,OAAO,iBAAS,eACdC,IAAI,qBACJC,cACAC,UAAU;AAKZ,OAAO,iBAAS,qBACdF,IAAI,qBACJC,cACAC,UAAU",
2
+ "mappings": "KAOK;KAEA,uBACHA,QAAQ,aACR,GAAG,gBACA,QAAQ;KAER,qBAAqB;CACxB;CACA;CACA;AACD;AAED,OAAO,iBAAS,eACdC,IAAI,qBACJC,cACAC,UAAU;AAKZ,OAAO,iBAAS,qBACdF,IAAI,qBACJC,cACAC,UAAU",
3
3
  "names": [
4
4
  "signal: AbortSignal",
5
5
  "cb: AsyncEffectCallback",
@@ -10,7 +10,7 @@
10
10
  "src/async/useAsyncEffect.ts"
11
11
  ],
12
12
  "sourcesContent": [
13
- "// adopted from https://github.com/franciscop/use-async/blob/master/src/index.js\n\nimport { useEffect, useId, useLayoutEffect } from 'react'\nimport { EMPTY_OBJECT } from '../constants'\nimport { handleAbortError } from './abortable'\n\ntype Cleanup = () => void\n\ntype AsyncEffectCallback = (\n signal: AbortSignal,\n ...deps: any[]\n) => Promise<Cleanup | void> | void\n\ntype AsyncEffectOptions = {\n circuitBreakAfter?: number\n circuitBreakPeriod?: number\n debug?: boolean\n}\n\nexport function useAsyncEffect(\n cb: AsyncEffectCallback,\n deps: any[] = [],\n options?: AsyncEffectOptions\n): void {\n useAsyncEffectImpl(false, cb, deps, options)\n}\n\nexport function useAsyncLayoutEffect(\n cb: AsyncEffectCallback,\n deps: any[] = [],\n options?: AsyncEffectOptions\n): void {\n useAsyncEffectImpl(true, cb, deps, options)\n}\n\nfunction useAsyncEffectImpl(\n isLayoutEffect: boolean,\n cb: AsyncEffectCallback,\n deps: any[] = [],\n options: AsyncEffectOptions = EMPTY_OBJECT\n): void {\n const effectHook = isLayoutEffect ? useLayoutEffect : useEffect\n // biome-ignore lint/correctness/useHookAtTopLevel: ignore\n const effectId = process.env.NODE_ENV === 'development' ? useId() : ''\n\n effectHook(() => {\n // Generate a unique ID for this effect instance for loop detection\n checkEffectLoop(effectId, options.circuitBreakAfter, options.circuitBreakPeriod)\n const controller = new AbortController()\n const signal = controller.signal\n\n // wrap in try in case its not async (for simple use cases)\n try {\n const value = cb(signal, ...deps)\n\n Promise.resolve(value)\n .then(async (res) => {\n if (res && typeof res === 'function') {\n if (signal.aborted) return res()\n signal.addEventListener('abort', res)\n }\n })\n .catch(handleAbortError)\n } catch (error) {\n handleAbortError(error, options.debug)\n }\n\n return () => {\n if (signal.aborted) return\n controller.abort()\n }\n }, deps)\n}\n\n// loop detection in dev mode\nlet effectRunCounts: Map<string, number[]>\nlet checkEffectLoop: (\n effectId: string,\n circuitBreakAfter?: number,\n circuitBreakPeriod?: number\n) => void\n\nif (process.env.NODE_ENV === 'development') {\n effectRunCounts = new Map<string, number[]>()\n\n checkEffectLoop = (\n effectId: string,\n circuitBreakAfter: number = 20,\n circuitBreakPeriod: number = 1000\n ) => {\n const now = Date.now()\n const runs = effectRunCounts.get(effectId) || []\n\n runs.push(now)\n\n // keep only runs from the specified period\n const recentRuns = runs.filter((time) => now - time < circuitBreakPeriod)\n effectRunCounts.set(effectId, recentRuns)\n\n const runCount = recentRuns.length\n\n if (runCount > circuitBreakAfter) {\n const message = `🚨 useAsyncEffect infinite loop detected! Effect ran ${runCount} times in <${circuitBreakPeriod}ms`\n if (process.env.NODE_ENV === 'development') {\n console.error(message)\n // biome-ignore lint/suspicious/noDebugger: ignore\n debugger\n } else {\n alert(message)\n throw new Error(message)\n }\n } else if (runCount > circuitBreakAfter / 2) {\n console.warn(\n `⚠️ useAsyncEffect potential loop: Effect ran ${runCount} times in <${circuitBreakPeriod}ms`\n )\n }\n }\n} else {\n checkEffectLoop = () => {}\n}\n"
13
+ "// adopted from https://github.com/franciscop/use-async/blob/master/src/index.js\n\nimport { useEffect, useId, useLayoutEffect } from 'react'\n\nimport { EMPTY_OBJECT } from '../constants'\nimport { handleAbortError } from './abortable'\n\ntype Cleanup = () => void\n\ntype AsyncEffectCallback = (\n signal: AbortSignal,\n ...deps: any[]\n) => Promise<Cleanup | void> | void\n\ntype AsyncEffectOptions = {\n circuitBreakAfter?: number\n circuitBreakPeriod?: number\n debug?: boolean\n}\n\nexport function useAsyncEffect(\n cb: AsyncEffectCallback,\n deps: any[] = [],\n options?: AsyncEffectOptions\n): void {\n useAsyncEffectImpl(false, cb, deps, options)\n}\n\nexport function useAsyncLayoutEffect(\n cb: AsyncEffectCallback,\n deps: any[] = [],\n options?: AsyncEffectOptions\n): void {\n useAsyncEffectImpl(true, cb, deps, options)\n}\n\nfunction useAsyncEffectImpl(\n isLayoutEffect: boolean,\n cb: AsyncEffectCallback,\n deps: any[] = [],\n options: AsyncEffectOptions = EMPTY_OBJECT\n): void {\n const effectHook = isLayoutEffect ? useLayoutEffect : useEffect\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const effectId = process.env.NODE_ENV === 'development' ? useId() : ''\n\n effectHook(() => {\n // Generate a unique ID for this effect instance for loop detection\n checkEffectLoop(effectId, options.circuitBreakAfter, options.circuitBreakPeriod)\n const controller = new AbortController()\n const signal = controller.signal\n\n // wrap in try in case its not async (for simple use cases)\n try {\n const value = cb(signal, ...deps)\n\n Promise.resolve(value)\n .then(async (res) => {\n if (res && typeof res === 'function') {\n if (signal.aborted) return res()\n signal.addEventListener('abort', res)\n }\n })\n .catch(handleAbortError)\n } catch (error) {\n handleAbortError(error, options.debug)\n }\n\n return () => {\n if (signal.aborted) return\n controller.abort()\n }\n }, deps)\n}\n\n// loop detection in dev mode\nlet effectRunCounts: Map<string, number[]>\nlet checkEffectLoop: (\n effectId: string,\n circuitBreakAfter?: number,\n circuitBreakPeriod?: number\n) => void\n\nif (process.env.NODE_ENV === 'development') {\n effectRunCounts = new Map<string, number[]>()\n\n checkEffectLoop = (\n effectId: string,\n circuitBreakAfter: number = 20,\n circuitBreakPeriod: number = 1000\n ) => {\n const now = Date.now()\n const runs = effectRunCounts.get(effectId) || []\n\n runs.push(now)\n\n // keep only runs from the specified period\n const recentRuns = runs.filter((time) => now - time < circuitBreakPeriod)\n effectRunCounts.set(effectId, recentRuns)\n\n const runCount = recentRuns.length\n\n if (runCount > circuitBreakAfter) {\n const message = `🚨 useAsyncEffect infinite loop detected! Effect ran ${runCount} times in <${circuitBreakPeriod}ms`\n if (process.env.NODE_ENV === 'development') {\n console.error(message)\n // eslint-disable-next-line no-debugger\n debugger\n } else {\n alert(message)\n throw new Error(message)\n }\n } else if (runCount > circuitBreakAfter / 2) {\n console.warn(\n `⚠️ useAsyncEffect potential loop: Effect ran ${runCount} times in <${circuitBreakPeriod}ms`\n )\n }\n }\n} else {\n checkEffectLoop = () => {}\n}\n"
14
14
  ],
15
15
  "version": 3
16
16
  }
@@ -1,5 +1,5 @@
1
- import type React from "react";
2
1
  import { type IdleOptions } from "./idle";
2
+ import type React from "react";
3
3
  export type LazyMountProps = IdleOptions;
4
4
  export declare const useLazyMount: (props?: LazyMountProps) => boolean;
5
5
  export declare const LazyMount: ({ children,...idleProps }: LazyMountProps & {
@@ -1,5 +1,5 @@
1
1
  {
2
- "mappings": "AAAA,YAAY,WAAW,OAAO;AAE9B,cAAoB,mBAAmB,QAAQ;AAG/C,YAAY,iBAAiB;AAE7B,OAAO,cAAM,eAAgBA,QAAO;AAkBpC,OAAO,cAAM,YAAa,EACxB,SACA,GAAG,WACgC,EAAlC,iBAAiB;CAAE;AAAe,MAAG,MAAM",
2
+ "mappings": "AAEA,cAAoB,mBAAmB,QAAQ;AAG/C,YAAY,WAAW,OAAO;AAE9B,YAAY,iBAAiB;AAE7B,OAAO,cAAM,eAAgBA,QAAO;AAkBpC,OAAO,cAAM,YAAa,EACxB,SACA,GAAG,WACgC,EAAlC,iBAAiB;CAAE;AAAe,MAAG,MAAM",
3
3
  "names": [
4
4
  "props: LazyMountProps"
5
5
  ],
@@ -7,7 +7,7 @@
7
7
  "src/async/useLazyMount.ts"
8
8
  ],
9
9
  "sourcesContent": [
10
- "import type React from 'react'\nimport { startTransition, useState } from 'react'\nimport { idle, type IdleOptions } from './idle'\nimport { useAsyncEffect } from './useAsyncEffect'\n\nexport type LazyMountProps = IdleOptions\n\nexport const useLazyMount = (props: LazyMountProps = { max: 100 }): boolean => {\n const [mounted, setMounted] = useState(false)\n\n useAsyncEffect(\n async (signal) => {\n await idle(props, signal)\n startTransition(() => {\n setMounted(true)\n })\n },\n [\n // no need for deps it only ever mounts once\n ]\n )\n\n return mounted\n}\n\nexport const LazyMount = ({\n children,\n ...idleProps\n}: LazyMountProps & { children: any }): React.ReactNode => {\n const mounted = useLazyMount(idleProps)\n return mounted ? children : null\n}\n"
10
+ "import { startTransition, useState } from 'react'\n\nimport { idle, type IdleOptions } from './idle'\nimport { useAsyncEffect } from './useAsyncEffect'\n\nimport type React from 'react'\n\nexport type LazyMountProps = IdleOptions\n\nexport const useLazyMount = (props: LazyMountProps = { max: 100 }): boolean => {\n const [mounted, setMounted] = useState(false)\n\n useAsyncEffect(\n async (signal) => {\n await idle(props, signal)\n startTransition(() => {\n setMounted(true)\n })\n },\n [\n // no need for deps it only ever mounts once\n ]\n )\n\n return mounted\n}\n\nexport const LazyMount = ({\n children,\n ...idleProps\n}: LazyMountProps & { children: any }): React.ReactNode => {\n const mounted = useLazyMount(idleProps)\n return mounted ? children : null\n}\n"
11
11
  ],
12
12
  "version": 3
13
13
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "mappings": "AACA,cAAoB,mBAAmB,QAAQ;AAG/C,OAAO,cAAM,eAAgB,GAC3BA,OAAO,GACP,EACE,qBACA,GAAG,aAC8C,GAAhD,cAAc;CAAE;AAAgC,MAClD",
2
+ "mappings": "AAEA,cAAoB,mBAAmB,QAAQ;AAG/C,OAAO,cAAM,eAAgB,GAC3BA,OAAO,GACP,EACE,qBACA,GAAG,aAC8C,GAAhD,cAAc;CAAE;AAAgC,MAClD",
3
3
  "names": [
4
4
  "value: T"
5
5
  ],
@@ -7,7 +7,7 @@
7
7
  "src/async/useLazyValue.ts"
8
8
  ],
9
9
  "sourcesContent": [
10
- "import { startTransition, useState } from 'react'\nimport { idle, type IdleOptions } from './idle'\nimport { useAsyncEffect } from './useAsyncEffect'\n\nexport const useLazyValue = <T>(\n value: T,\n {\n immediateFirstUpdate,\n ...idleOptions\n }: IdleOptions & { immediateFirstUpdate?: boolean } = {}\n): T => {\n const [lazyValue, setLazyValue] = useState(value)\n\n // first update to a real value immediate\n if (value && lazyValue === undefined && lazyValue !== value && immediateFirstUpdate) {\n setLazyValue(value)\n }\n\n useAsyncEffect(\n async (signal) => {\n await idle(idleOptions, signal)\n startTransition(() => {\n setLazyValue(value)\n })\n },\n [value]\n )\n\n return lazyValue\n}\n"
10
+ "import { startTransition, useState } from 'react'\n\nimport { idle, type IdleOptions } from './idle'\nimport { useAsyncEffect } from './useAsyncEffect'\n\nexport const useLazyValue = <T>(\n value: T,\n {\n immediateFirstUpdate,\n ...idleOptions\n }: IdleOptions & { immediateFirstUpdate?: boolean } = {}\n): T => {\n const [lazyValue, setLazyValue] = useState(value)\n\n // first update to a real value immediate\n if (value && lazyValue === undefined && lazyValue !== value && immediateFirstUpdate) {\n setLazyValue(value)\n }\n\n useAsyncEffect(\n async (signal) => {\n await idle(idleOptions, signal)\n startTransition(() => {\n setLazyValue(value)\n })\n },\n [value]\n )\n\n return lazyValue\n}\n"
11
11
  ],
12
12
  "version": 3
13
13
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "mappings": "AACA,cAAc,KAAK,yBAAyB,OAAO;KA+B9C,eAAe,KAAK,kBAAkB,KAAK;CAC9C;AACD;KAEI,kBAAkB,KAAK;CAC1B;CACA,cAAcA,GAAG,GAAGC,GAAG;AACxB;AAED,OAAO,cAAM,cAAc,GAAG;CAC5B,QAAQ;CACR,OAAO;CACP,UAAU,eAAe;CAEzB,YAAYC,OAAO,GAAGC,UAAU,eAAe;CAK/C,SAAUC,aAAaC,IAAI;CAO3B,OAAQC,MAAM;CAqCd,iBAAgB,QAAQ;AAQzB;;AAGD,OAAO,iBAAS,oBAAoB,GAClCC,cACAC,cAAc,GACdC,UAAU,kBAAkB,KAC3B,QAAQ;AAIX,OAAO,iBAAS,cAAc,GAC5BF,cACAC,cAAc,GACdC,UAAU,kBAAkB,KAC3B,QAAQ;AAKX,YAAY,YAAY,UAAU,gBAAgB,UAAU,cAAc,OACtE;AAGJ,OAAO,cAAM,aAAc,UAAU,cACnCC,SAAS,GACTC,KAAKC,IAAI,YAAY,aACrBC;AAeF,OAAO,cAAM,kBAAmB,UAAU,cACxCH,SAAS,GACTI,UAAU;CAAE;AAAmB,MAC9B,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCf,OAAO,cAAM;CAAsB,UAAU;CAAc,UAAU,YAAY;CAAI;EACnFJ,SAAS,GACTK,WAAWb,OAAO,MAAM,GACxBc,UAAU;CACR;CACA;AACD,GACDC,iBACC;AAgCH,OAAO,cAAM;OAA6B,mBAAmB;CAAgB;EAC3EC,UAAU,GACVC,WAAWC,WAAW,WAAW,KAAI,YAAY,EAAE,UAAU,GAC7DC,UAAU;CAAE;CAAmB,WAAWC,GAAG,GAAGC,GAAG;AAAe,MACjE;AAgDH,OAAO,cAAM,mBAAoB,UAAU,cACzCb,SAAS,QACNc,KAAKC,KAAK,YAAY,aAAaZ;AAKxC,OAAO,cAAM,oBAAqB,UAAU,cAC1CH,SAAS,QACN,GACHgB,WAAWC,OAAO,YAAY,OAAO,GACrCC,UAAU;CAAE;CAAmB;AAAgB,GAC/Cf,iBACG;AAUL,OAAO,iBAAS,wBAAwB,GACtCN,cACAC,cAAc,GACdqB,iBAAiB,KAAK,eAAe,IAAI,0BAEnC,QAAQ,KACbC,OAAO,kBAAkB;CAAE,QAAQ;CAAG;AAAkB,OAAM,IAAI",
2
+ "mappings": "AAUA,cAAc,KAAK,yBAAyB,OAAO;KAwB9C,eAAe,KAAK,kBAAkB,KAAK;CAC9C;AACD;KAEI,kBAAkB,KAAK;CAC1B;CACA,cAAcA,GAAG,GAAGC,GAAG;AACxB;AAED,OAAO,cAAM,cAAc,GAAG;CAC5B,QAAQ;CACR,OAAO;CACP,UAAU,eAAe;CAEzB,YAAYC,OAAO,GAAGC,UAAU,eAAe;CAK/C,SAAUC,aAAaC,IAAI;CAO3B,OAAQC,MAAM;CAqCd,iBAAgB,QAAQ;AAQzB;;AAGD,OAAO,iBAAS,oBAAoB,GAClCC,cACAC,cAAc,GACdC,UAAU,kBAAkB,KAC3B,QAAQ;AAIX,OAAO,iBAAS,cAAc,GAC5BF,cACAC,cAAc,GACdC,UAAU,kBAAkB,KAC3B,QAAQ;AAKX,YAAY,YAAY,UAAU,gBAChC,UAAU,cAAc,OAAO;AAEjC,OAAO,cAAM,aAAc,UAAU,cACnCC,SAAS,GACTC,KAAKC,IAAI,YAAY,aACrBC;AAeF,OAAO,cAAM,kBAAmB,UAAU,cACxCH,SAAS,GACTI,UAAU;CAAE;AAAmB,MAC9B,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCf,OAAO,cAAM;CAAsB,UAAU;CAAc,UAAU,YAAY;CAAI;EACnFJ,SAAS,GACTK,WAAWb,OAAO,MAAM,GACxBc,UAAU;CACR;CACA;AACD,GACDC,iBACC;AAgCH,OAAO,cAAM;OAA6B,mBAAmB;CAAgB;EAC3EC,UAAU,GACVC,WAAWC,WAAW,WAAW,KAAI,YAAY,EAAE,UAAU,GAC7DC,UAAU;CAAE;CAAmB,WAAWC,GAAG,GAAGC,GAAG;AAAe,MACjE;AAgDH,OAAO,cAAM,mBAAoB,UAAU,cACzCb,SAAS,QACNc,KAAKC,KAAK,YAAY,aAAaZ;AAKxC,OAAO,cAAM,oBAAqB,UAAU,cAC1CH,SAAS,QACN,GACHgB,WAAWC,OAAO,YAAY,OAAO,GACrCC,UAAU;CAAE;CAAmB;AAAgB,GAC/Cf,iBACG;AAUL,OAAO,iBAAS,wBAAwB,GACtCN,cACAC,cAAc,GACdqB,iBAAiB,KAAK,eAAe,IAAI,0BAEnC,QAAQ,KACbC,OAAO,kBAAkB;CAAE,QAAQ;CAAG;AAAkB,OAAM,IAAI",
3
3
  "names": [
4
4
  "a: T",
5
5
  "b: T",
@@ -37,7 +37,7 @@
37
37
  "src/emitter.tsx"
38
38
  ],
39
39
  "sourcesContent": [
40
- "import { dequal } from 'dequal'\nimport type { JSX, PropsWithChildren } from 'react'\nimport * as React from 'react'\nimport { use, useLayoutEffect, useState } from 'react'\nimport { handleAbortError } from './async/abortable'\nimport { DEBUG_LEVEL, EMPTY_ARRAY } from './constants'\nimport { AbortError } from './error/errors'\nimport { createGlobalContext } from './react/createGlobalContext'\nimport { globalValue } from './global/globalValue'\n\n// keeps a reference to the current value easily\n\n// TODO can replace with useEffectEvent\nfunction useGet<A>(\n currentValue: A,\n initialValue?: any,\n forwardToFunction?: boolean\n): () => A {\n const curRef = React.useRef<any>(initialValue ?? currentValue)\n\n useLayoutEffect(() => {\n curRef.current = currentValue\n })\n\n return React.useCallback(\n forwardToFunction\n ? (...args) => curRef.current?.apply(null, args)\n : () => curRef.current,\n []\n )\n}\n\ntype EmitterOptions<T> = CreateEmitterOpts<T> & {\n name: string\n}\n\ntype CreateEmitterOpts<T> = {\n silent?: boolean\n comparator?: (a: T, b: T) => boolean\n}\n\nexport class Emitter<const T> {\n private disposables = new Set<(cb: any) => void>()\n value: T\n options?: EmitterOptions<T>\n\n constructor(value: T, options?: EmitterOptions<T>) {\n this.value = value\n this.options = options\n }\n\n listen = (disposable: (cb: T) => void): (() => void) => {\n this.disposables.add(disposable)\n return (): void => {\n this.disposables.delete(disposable)\n }\n }\n\n emit = (next: T): void => {\n if (process.env.NODE_ENV === 'development') {\n setCache(this, next)\n }\n const compare = this.options?.comparator\n if (compare) {\n if (this.value && compare(this.value, next)) {\n return\n }\n } else {\n if (this.value === next) {\n if (process.env.NODE_ENV === 'development') {\n console.warn(\n `[emitter] ${this.options?.name} no comparator option but received same value!\n \nthis will emit the same value again, which can be desirable, but we warn to ensure it's not unintended:\n\n- if you want this behavior, add { comparator: isEqualNever }\n- if you want only non-equal values: { comparator: isEqualIdentity }\n- if you want only deeply non-equal values: { comparator: isEqualDeep }`\n )\n }\n }\n }\n this.value = next\n if (DEBUG_LEVEL > 1) {\n if (!this.options?.silent) {\n const name = this.options?.name\n console.groupCollapsed(`📣 ${name}`)\n console.info(next)\n console.trace(`trace >`)\n console.groupEnd()\n }\n }\n this.disposables.forEach((cb) => cb(next))\n }\n\n nextValue = (): Promise<T> => {\n return new Promise<T>((res) => {\n const dispose = this.listen((val) => {\n dispose()\n res(val)\n })\n })\n }\n}\n\n// just createEmitter but ensures it doesn't mess up on HMR\nexport function createGlobalEmitter<T>(\n name: string,\n defaultValue: T,\n options?: CreateEmitterOpts<T>\n): Emitter<T> {\n return globalValue(name, () => createEmitter(name, defaultValue, options))\n}\n\nexport function createEmitter<T>(\n name: string,\n defaultValue: T,\n options?: CreateEmitterOpts<T>\n): Emitter<T> {\n const existing = createOrUpdateCache(name, defaultValue) as T\n return new Emitter<T>(existing || defaultValue, { name, ...options })\n}\n\nexport type EmitterType<E extends Emitter<any>> = E extends Emitter<infer Val>\n ? Val\n : never\n\nexport const useEmitter = <E extends Emitter<any>>(\n emitter: E,\n cb: (cb: EmitterType<E>) => void,\n args?: any[]\n): void => {\n const getCallback = useGet(cb)\n\n useLayoutEffect(() => {\n return emitter.listen((val) => {\n try {\n getCallback()(val)\n } catch (err) {\n handleAbortError(err)\n }\n })\n }, [emitter, getCallback])\n}\n\nexport const useEmitterValue = <E extends Emitter<any>>(\n emitter: E,\n options?: { disable?: boolean }\n): EmitterType<E> => {\n const [state, setState] = useState<EmitterType<E>>(emitter.value)\n const disabled = options?.disable\n\n useLayoutEffect(() => {\n if (disabled) return\n if (emitter.value !== state) {\n setState(emitter.value)\n }\n return emitter.listen(setState)\n }, [disabled, emitter, state])\n\n return state\n}\n\n/**\n * By default selectors run every render, as well as when emitters update. This is a change\n * from the previous behavior where they only ran when emitters changed value.\n *\n * The reason for this is because emitters capture the variables in scope of the component\n * each render already by using \"useGet\" by default, which makes them easier to use - you\n * don't need to pass an args[] array except for edge cases.\n *\n * Before explaining why we switched to the default, understand the different uses:\n *\n * - Default behavior - selector is updated every render, and ran every render, as well as\n * when emitter value changes, so you basically are always up to date.\n *\n * - Set an args[] array as the fourth argument - this will stop the automatic capturing\n * and instead update selector only when you change args[] yourself. This is good for when you\n * want explicit control over re-selection and rendering.\n *\n * - With { lazy: true }, the selector only runs when the emitter value changes. If used with\n * args[], you capture the context of the selector based on args[], if not, it's based on the\n * current render.\n *\n * I made this change when we had 16 usages of useEmitterSelector and 100% of them are doing very\n * cheap calculations, so this feels like the right pattern. For the rare case of a heavy selector,\n * you have the option to control it.\n *\n */\nexport const useEmitterSelector = <E extends Emitter<any>, T extends EmitterType<E>, R>(\n emitter: E,\n selector: (value: T) => R,\n options?: {\n disable?: boolean\n lazy?: boolean\n },\n args: any[] = EMPTY_ARRAY\n): R => {\n const [state, setState] = useState<R>(() => selector(emitter.value))\n const disabled = options?.disable\n const getSelector = useGet(selector)\n\n if (options?.lazy !== true) {\n const next = selector(emitter.value)\n if (next !== state) {\n setState(next)\n }\n }\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: we are using args fine\n useLayoutEffect(() => {\n if (disabled) return\n return emitter.listen((val) => {\n try {\n const selectorFn = args !== EMPTY_ARRAY ? selector : getSelector()\n const next = selectorFn(val)\n setState(next)\n } catch (error) {\n if (error instanceof AbortError) {\n return\n }\n throw error\n }\n })\n }, [disabled, emitter, getSelector, ...args])\n\n return state\n}\n\nexport const useEmittersSelector = <const E extends readonly Emitter<any>[], R>(\n emitters: E,\n selector: (values: { [K in keyof E]: EmitterType<E[K]> }) => R,\n options?: { disable?: boolean; isEqual?: (a: R, b: R) => boolean }\n): R => {\n const getSelector = useGet(selector)\n const disabled = options?.disable\n\n const [state, setState] = useState<R>(() => {\n const values = emitters.map((e) => e.value) as { [K in keyof E]: EmitterType<E[K]> }\n return getSelector()(values)\n })\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: ignore\n useLayoutEffect(() => {\n if (disabled) {\n return\n }\n\n const handler = () => {\n const values = emitters.map((e) => e.value) as {\n [K in keyof E]: EmitterType<E[K]>\n }\n try {\n const next = getSelector()(values)\n setState((prev) => {\n if (options?.isEqual?.(prev, next)) {\n return prev\n }\n if (dequal(prev, next)) {\n return prev\n }\n return next\n })\n } catch (error) {\n if (error instanceof AbortError) {\n return\n }\n throw error\n }\n }\n\n const disposals = emitters.map((emitter) => emitter.listen(handler))\n\n return () => {\n disposals.forEach((d) => d())\n }\n }, [disabled, getSelector, ...emitters])\n\n return state\n}\n\nexport const createUseEmitter = <E extends Emitter<any>>(\n emitter: E\n): ((cb: (val: EmitterType<E>) => void, args?: any[]) => void) => {\n return (cb: (val: EmitterType<E>) => void, args?: any[]) =>\n useEmitter(emitter, cb, args)\n}\n\nexport const createUseSelector = <E extends Emitter<any>>(\n emitter: E\n): (<R>(\n selector: (value: EmitterType<E>) => R,\n options?: { disable?: boolean; lazy?: boolean },\n args?: any[]\n) => R) => {\n return <R,>(\n selector: (value: EmitterType<E>) => R,\n options?: { disable?: boolean; lazy?: boolean },\n args?: any[]\n ): R => {\n return useEmitterSelector(emitter, selector, options, args)\n }\n}\n\nexport function createContextualEmitter<T>(\n name: string,\n defaultValue: T,\n defaultOptions?: Omit<EmitterOptions<T>, 'name'>\n): readonly [\n () => Emitter<T>,\n (props: PropsWithChildren<{ value?: T; silent?: boolean }>) => JSX.Element,\n] {\n const id = Math.random().toString(36)\n const EmitterContext = createGlobalContext<Emitter<T> | null>(\n `contextual-emitter/${id}`,\n null\n )\n\n const useContextEmitter = () => {\n const emitter = use(EmitterContext)\n if (!emitter) {\n throw new Error('useContextEmitter must be used within an EmitterProvider')\n }\n return emitter\n }\n\n type ProvideEmitterProps = PropsWithChildren<{\n value?: T\n silent?: boolean\n }>\n\n const ProvideEmitter = (props: ProvideEmitterProps) => {\n const { children, value, silent } = props\n const [emitter] = useState(\n () => new Emitter<T>(value ?? defaultValue, { name, silent, ...defaultOptions })\n )\n\n useLayoutEffect(() => {\n if (value !== undefined && value !== emitter.value) {\n emitter.emit(value)\n }\n }, [value, emitter])\n\n return <EmitterContext.Provider value={emitter}>{children}</EmitterContext.Provider>\n }\n\n return [useContextEmitter, ProvideEmitter] as const\n}\n\nconst HMRCache =\n process.env.NODE_ENV === 'development'\n ? new Map<string, { originalDefaultValue: unknown; currentValue: unknown }>()\n : null\n\nfunction setCache(emitter: Emitter<any>, value: unknown) {\n const name = emitter.options?.name\n if (!name) return\n const cache = HMRCache?.get(name)\n if (!cache) return\n cache.currentValue = value\n}\n\nfunction createOrUpdateCache(name: string, defaultValueProp: unknown) {\n const existing = HMRCache?.get(name)\n const defaultValue = dequal(existing?.originalDefaultValue, defaultValueProp)\n ? existing?.currentValue\n : defaultValueProp\n\n if (!existing) {\n HMRCache?.set(name, {\n originalDefaultValue: defaultValueProp,\n currentValue: defaultValue,\n })\n }\n\n return defaultValue\n}\n"
40
+ "import { dequal } from 'dequal'\nimport * as React from 'react'\nimport { use, useLayoutEffect, useState } from 'react'\n\nimport { handleAbortError } from './async/abortable'\nimport { DEBUG_LEVEL, EMPTY_ARRAY } from './constants'\nimport { AbortError } from './error/errors'\nimport { globalValue } from './global/globalValue'\nimport { createGlobalContext } from './react/createGlobalContext'\n\nimport type { JSX, PropsWithChildren } from 'react'\n\n// keeps a reference to the current value easily\n\n// TODO can replace with useEffectEvent\nfunction useGet<A>(\n currentValue: A,\n initialValue?: any,\n forwardToFunction?: boolean\n): () => A {\n const curRef = React.useRef<any>(initialValue ?? currentValue)\n\n useLayoutEffect(() => {\n curRef.current = currentValue\n })\n\n return React.useCallback(\n forwardToFunction\n ? (...args) => curRef.current?.apply(null, args)\n : () => curRef.current,\n []\n )\n}\n\ntype EmitterOptions<T> = CreateEmitterOpts<T> & {\n name: string\n}\n\ntype CreateEmitterOpts<T> = {\n silent?: boolean\n comparator?: (a: T, b: T) => boolean\n}\n\nexport class Emitter<const T> {\n private disposables = new Set<(cb: any) => void>()\n value: T\n options?: EmitterOptions<T>\n\n constructor(value: T, options?: EmitterOptions<T>) {\n this.value = value\n this.options = options\n }\n\n listen = (disposable: (cb: T) => void): (() => void) => {\n this.disposables.add(disposable)\n return (): void => {\n this.disposables.delete(disposable)\n }\n }\n\n emit = (next: T): void => {\n if (process.env.NODE_ENV === 'development') {\n setCache(this, next)\n }\n const compare = this.options?.comparator\n if (compare) {\n if (this.value && compare(this.value, next)) {\n return\n }\n } else {\n if (this.value === next) {\n if (process.env.NODE_ENV === 'development') {\n console.warn(\n `[emitter] ${this.options?.name} no comparator option but received same value!\n \nthis will emit the same value again, which can be desirable, but we warn to ensure it's not unintended:\n\n- if you want this behavior, add { comparator: isEqualNever }\n- if you want only non-equal values: { comparator: isEqualIdentity }\n- if you want only deeply non-equal values: { comparator: isEqualDeep }`\n )\n }\n }\n }\n this.value = next\n if (DEBUG_LEVEL > 1) {\n if (!this.options?.silent) {\n const name = this.options?.name\n console.groupCollapsed(`📣 ${name}`)\n console.info(next)\n console.trace(`trace >`)\n console.groupEnd()\n }\n }\n this.disposables.forEach((cb) => cb(next))\n }\n\n nextValue = (): Promise<T> => {\n return new Promise<T>((res) => {\n const dispose = this.listen((val) => {\n dispose()\n res(val)\n })\n })\n }\n}\n\n// just createEmitter but ensures it doesn't mess up on HMR\nexport function createGlobalEmitter<T>(\n name: string,\n defaultValue: T,\n options?: CreateEmitterOpts<T>\n): Emitter<T> {\n return globalValue(name, () => createEmitter(name, defaultValue, options))\n}\n\nexport function createEmitter<T>(\n name: string,\n defaultValue: T,\n options?: CreateEmitterOpts<T>\n): Emitter<T> {\n const existing = createOrUpdateCache(name, defaultValue) as T\n return new Emitter<T>(existing || defaultValue, { name, ...options })\n}\n\nexport type EmitterType<E extends Emitter<any>> =\n E extends Emitter<infer Val> ? Val : never\n\nexport const useEmitter = <E extends Emitter<any>>(\n emitter: E,\n cb: (cb: EmitterType<E>) => void,\n args?: any[]\n): void => {\n const getCallback = useGet(cb)\n\n useLayoutEffect(() => {\n return emitter.listen((val) => {\n try {\n getCallback()(val)\n } catch (err) {\n handleAbortError(err)\n }\n })\n }, [emitter, getCallback])\n}\n\nexport const useEmitterValue = <E extends Emitter<any>>(\n emitter: E,\n options?: { disable?: boolean }\n): EmitterType<E> => {\n const [state, setState] = useState<EmitterType<E>>(emitter.value)\n const disabled = options?.disable\n\n useLayoutEffect(() => {\n if (disabled) return\n if (emitter.value !== state) {\n setState(emitter.value)\n }\n return emitter.listen(setState)\n }, [disabled, emitter, state])\n\n return state\n}\n\n/**\n * By default selectors run every render, as well as when emitters update. This is a change\n * from the previous behavior where they only ran when emitters changed value.\n *\n * The reason for this is because emitters capture the variables in scope of the component\n * each render already by using \"useGet\" by default, which makes them easier to use - you\n * don't need to pass an args[] array except for edge cases.\n *\n * Before explaining why we switched to the default, understand the different uses:\n *\n * - Default behavior - selector is updated every render, and ran every render, as well as\n * when emitter value changes, so you basically are always up to date.\n *\n * - Set an args[] array as the fourth argument - this will stop the automatic capturing\n * and instead update selector only when you change args[] yourself. This is good for when you\n * want explicit control over re-selection and rendering.\n *\n * - With { lazy: true }, the selector only runs when the emitter value changes. If used with\n * args[], you capture the context of the selector based on args[], if not, it's based on the\n * current render.\n *\n * I made this change when we had 16 usages of useEmitterSelector and 100% of them are doing very\n * cheap calculations, so this feels like the right pattern. For the rare case of a heavy selector,\n * you have the option to control it.\n *\n */\nexport const useEmitterSelector = <E extends Emitter<any>, T extends EmitterType<E>, R>(\n emitter: E,\n selector: (value: T) => R,\n options?: {\n disable?: boolean\n lazy?: boolean\n },\n args: any[] = EMPTY_ARRAY\n): R => {\n const [state, setState] = useState<R>(() => selector(emitter.value))\n const disabled = options?.disable\n const getSelector = useGet(selector)\n\n if (options?.lazy !== true) {\n const next = selector(emitter.value)\n if (next !== state) {\n setState(next)\n }\n }\n\n // eslint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(() => {\n if (disabled) return\n return emitter.listen((val) => {\n try {\n const selectorFn = args !== EMPTY_ARRAY ? selector : getSelector()\n const next = selectorFn(val)\n setState(next)\n } catch (error) {\n if (error instanceof AbortError) {\n return\n }\n throw error\n }\n })\n }, [disabled, emitter, getSelector, ...args])\n\n return state\n}\n\nexport const useEmittersSelector = <const E extends readonly Emitter<any>[], R>(\n emitters: E,\n selector: (values: { [K in keyof E]: EmitterType<E[K]> }) => R,\n options?: { disable?: boolean; isEqual?: (a: R, b: R) => boolean }\n): R => {\n const getSelector = useGet(selector)\n const disabled = options?.disable\n\n const [state, setState] = useState<R>(() => {\n const values = emitters.map((e) => e.value) as { [K in keyof E]: EmitterType<E[K]> }\n return getSelector()(values)\n })\n\n // eslint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(() => {\n if (disabled) {\n return\n }\n\n const handler = () => {\n const values = emitters.map((e) => e.value) as {\n [K in keyof E]: EmitterType<E[K]>\n }\n try {\n const next = getSelector()(values)\n setState((prev) => {\n if (options?.isEqual?.(prev, next)) {\n return prev\n }\n if (dequal(prev, next)) {\n return prev\n }\n return next\n })\n } catch (error) {\n if (error instanceof AbortError) {\n return\n }\n throw error\n }\n }\n\n const disposals = emitters.map((emitter) => emitter.listen(handler))\n\n return () => {\n disposals.forEach((d) => d())\n }\n }, [disabled, getSelector, ...emitters])\n\n return state\n}\n\nexport const createUseEmitter = <E extends Emitter<any>>(\n emitter: E\n): ((cb: (val: EmitterType<E>) => void, args?: any[]) => void) => {\n return (cb: (val: EmitterType<E>) => void, args?: any[]) =>\n useEmitter(emitter, cb, args)\n}\n\nexport const createUseSelector = <E extends Emitter<any>>(\n emitter: E\n): (<R>(\n selector: (value: EmitterType<E>) => R,\n options?: { disable?: boolean; lazy?: boolean },\n args?: any[]\n) => R) => {\n return <R,>(\n selector: (value: EmitterType<E>) => R,\n options?: { disable?: boolean; lazy?: boolean },\n args?: any[]\n ): R => {\n return useEmitterSelector(emitter, selector, options, args)\n }\n}\n\nexport function createContextualEmitter<T>(\n name: string,\n defaultValue: T,\n defaultOptions?: Omit<EmitterOptions<T>, 'name'>\n): readonly [\n () => Emitter<T>,\n (props: PropsWithChildren<{ value?: T; silent?: boolean }>) => JSX.Element,\n] {\n const id = Math.random().toString(36)\n const EmitterContext = createGlobalContext<Emitter<T> | null>(\n `contextual-emitter/${id}`,\n null\n )\n\n const useContextEmitter = () => {\n const emitter = use(EmitterContext)\n if (!emitter) {\n throw new Error('useContextEmitter must be used within an EmitterProvider')\n }\n return emitter\n }\n\n type ProvideEmitterProps = PropsWithChildren<{\n value?: T\n silent?: boolean\n }>\n\n const ProvideEmitter = (props: ProvideEmitterProps) => {\n const { children, value, silent } = props\n const [emitter] = useState(\n () => new Emitter<T>(value ?? defaultValue, { name, silent, ...defaultOptions })\n )\n\n useLayoutEffect(() => {\n if (value !== undefined && value !== emitter.value) {\n emitter.emit(value)\n }\n }, [value, emitter])\n\n return <EmitterContext.Provider value={emitter}>{children}</EmitterContext.Provider>\n }\n\n return [useContextEmitter, ProvideEmitter] as const\n}\n\nconst HMRCache =\n process.env.NODE_ENV === 'development'\n ? new Map<string, { originalDefaultValue: unknown; currentValue: unknown }>()\n : null\n\nfunction setCache(emitter: Emitter<any>, value: unknown) {\n const name = emitter.options?.name\n if (!name) return\n const cache = HMRCache?.get(name)\n if (!cache) return\n cache.currentValue = value\n}\n\nfunction createOrUpdateCache(name: string, defaultValueProp: unknown) {\n const existing = HMRCache?.get(name)\n const defaultValue = dequal(existing?.originalDefaultValue, defaultValueProp)\n ? existing?.currentValue\n : defaultValueProp\n\n if (!existing) {\n HMRCache?.set(name, {\n originalDefaultValue: defaultValueProp,\n currentValue: defaultValue,\n })\n }\n\n return defaultValue\n}\n"
41
41
  ],
42
42
  "version": 3
43
43
  }
package/types/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  export * from "./constants";
2
2
  export * from "./emitter";
3
3
  // array
4
- export * from "./array/fuzzy";
5
4
  export * from "./array/getRandomItem";
6
5
  export * from "./array/takeLast";
7
6
  export * from "./array/uniqBy";
@@ -21,7 +20,6 @@ export * from "./async/useLazyValue";
21
20
  // browser
22
21
  export * from "./browser/clearIndexedDB";
23
22
  export * from "./browser/isActiveElementFormField";
24
- export * from "./browser/localStorage";
25
23
  export * from "./browser/openPopup";
26
24
  // debug
27
25
  export * from "./debug/debugLog";
@@ -54,6 +52,8 @@ export * from "./object/objectUniqueKey";
54
52
  // react
55
53
  export * from "./react/createGlobalContext";
56
54
  export * from "./react/getCurrentComponentStack";
55
+ // storage
56
+ export * from "./storage";
57
57
  // server
58
58
  export * from "./server/ensureEnv";
59
59
  export * from "./server/getHeaders";
@@ -1,11 +1,11 @@
1
1
  {
2
- "mappings": "AAAA,cAAc;AACd,cAAc;;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;;AAGd,cAAc;;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;;AAGd,cAAc;AACd,cAAc;;AAGd,cAAc;AACd,cAAc;;AAGd,cAAc;;;AAKd,cAAc;AACd,cAAc;AACd,cAAc;;AAGd,cAAc;AACd,cAAc;;AAGd,cAAc;;AAGd,cAAc;AACd,cAAc;AACd,SAAS,uBAAuB;AAChC,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;;AAGd,cAAc;AACd,cAAc;;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;;AAGd,cAAc;AACd,cAAc;AACd,cAAc;;AAGd,mBAAmB;AACnB,mBAAmB;AACnB,mBAAmB;AACnB,mBAAmB;AACnB,mBAAmB;;AAGnB,cAAc;AACd,cAAc",
2
+ "mappings": "AAAA,cAAc;AACd,cAAc;;AAGd,cAAc;AACd,cAAc;AACd,cAAc;;AAGd,cAAc;;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;;AAGd,cAAc;AACd,cAAc;AACd,cAAc;;AAGd,cAAc;AACd,cAAc;;AAGd,cAAc;AACd,cAAc;;AAGd,cAAc;;;AAKd,cAAc;AACd,cAAc;AACd,cAAc;;AAGd,cAAc;AACd,cAAc;;AAGd,cAAc;;AAGd,cAAc;AACd,cAAc;AACd,SAAS,uBAAuB;AAChC,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;;AAGd,cAAc;AACd,cAAc;;AAGd,cAAc;;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;;AAGd,cAAc;AACd,cAAc;AACd,cAAc;;AAGd,mBAAmB;AACnB,mBAAmB;AACnB,mBAAmB;AACnB,mBAAmB;AACnB,mBAAmB;;AAGnB,cAAc;AACd,cAAc",
3
3
  "names": [],
4
4
  "sources": [
5
5
  "src/index.ts"
6
6
  ],
7
7
  "sourcesContent": [
8
- "export * from './constants'\nexport * from './emitter'\n\n// array\nexport * from './array/fuzzy'\nexport * from './array/getRandomItem'\nexport * from './array/takeLast'\nexport * from './array/uniqBy'\n\n// assert\nexport * from './assert'\n\n// async\nexport * from './async/abortable'\nexport * from './async/asyncContext'\nexport * from './async/idle'\nexport * from './async/interval'\nexport * from './async/isAborted'\nexport * from './async/sleep'\nexport * from './async/useAsync'\nexport * from './async/useAsyncEffect'\nexport * from './async/useLazyMount'\nexport * from './async/useLazyValue'\n\n// browser\nexport * from './browser/clearIndexedDB'\nexport * from './browser/isActiveElementFormField'\nexport * from './browser/localStorage'\nexport * from './browser/openPopup'\n\n// debug\nexport * from './debug/debugLog'\nexport * from './debug/debugUseState'\n\n// ensure\nexport * from './ensure/ensure'\nexport * from './ensure/ensureOne'\n\n// error\nexport * from './error/errors'\n\n// files\n\n// function\nexport * from './function/emptyFn'\nexport * from './function/identityFn'\nexport * from './function/throttle'\n\n// global\nexport * from './global/globalEffect'\nexport * from './global/globalValue'\n\n// number\nexport * from './number/formatNumber'\n\n// object\nexport * from './object/decorateObject'\nexport * from './object/isEqualDeep'\nexport { isEqualDeepLite } from './object/isEqualDeep'\nexport * from './object/isEqualIdentity'\nexport * from './object/isEqualJSON'\nexport * from './object/isEqualNever'\nexport * from './object/mapObject'\nexport * from './object/object'\nexport * from './object/objectUniqueKey'\n\n// react\nexport * from './react/createGlobalContext'\nexport * from './react/getCurrentComponentStack'\n\n// server\nexport * from './server/ensureEnv'\nexport * from './server/getHeaders'\nexport * from './server/prettyPrintRequest'\nexport * from './server/prettyPrintResponse'\nexport * from './server/streamToString'\n\n// string\nexport * from './string/dedent'\nexport * from './string/ellipsis'\nexport * from './string/hash'\nexport * from './string/insertAtIndex'\nexport * from './string/pickLast'\nexport * from './string/pluralize'\nexport * from './string/randomId'\nexport * from './string/slugify'\nexport * from './string/truncateList'\n\n// time\nexport * from './time/formatDate'\nexport * from './time/formatDateRelative'\nexport * from './time/time'\n\n// types\nexport type * from './types/NullToOptional'\nexport type * from './types/object'\nexport type * from './types/react'\nexport type * from './types/timer'\nexport type * from './types/tuple'\n\n// url\nexport * from './url/urlSanitize'\nexport * from './url/urlValidate'\n"
8
+ "export * from './constants'\nexport * from './emitter'\n\n// array\nexport * from './array/getRandomItem'\nexport * from './array/takeLast'\nexport * from './array/uniqBy'\n\n// assert\nexport * from './assert'\n\n// async\nexport * from './async/abortable'\nexport * from './async/asyncContext'\nexport * from './async/idle'\nexport * from './async/interval'\nexport * from './async/isAborted'\nexport * from './async/sleep'\nexport * from './async/useAsync'\nexport * from './async/useAsyncEffect'\nexport * from './async/useLazyMount'\nexport * from './async/useLazyValue'\n\n// browser\nexport * from './browser/clearIndexedDB'\nexport * from './browser/isActiveElementFormField'\nexport * from './browser/openPopup'\n\n// debug\nexport * from './debug/debugLog'\nexport * from './debug/debugUseState'\n\n// ensure\nexport * from './ensure/ensure'\nexport * from './ensure/ensureOne'\n\n// error\nexport * from './error/errors'\n\n// files\n\n// function\nexport * from './function/emptyFn'\nexport * from './function/identityFn'\nexport * from './function/throttle'\n\n// global\nexport * from './global/globalEffect'\nexport * from './global/globalValue'\n\n// number\nexport * from './number/formatNumber'\n\n// object\nexport * from './object/decorateObject'\nexport * from './object/isEqualDeep'\nexport { isEqualDeepLite } from './object/isEqualDeep'\nexport * from './object/isEqualIdentity'\nexport * from './object/isEqualJSON'\nexport * from './object/isEqualNever'\nexport * from './object/mapObject'\nexport * from './object/object'\nexport * from './object/objectUniqueKey'\n\n// react\nexport * from './react/createGlobalContext'\nexport * from './react/getCurrentComponentStack'\n\n// storage\nexport * from './storage'\n\n// server\nexport * from './server/ensureEnv'\nexport * from './server/getHeaders'\nexport * from './server/prettyPrintRequest'\nexport * from './server/prettyPrintResponse'\nexport * from './server/streamToString'\n\n// string\nexport * from './string/dedent'\nexport * from './string/ellipsis'\nexport * from './string/hash'\nexport * from './string/insertAtIndex'\nexport * from './string/pickLast'\nexport * from './string/pluralize'\nexport * from './string/randomId'\nexport * from './string/slugify'\nexport * from './string/truncateList'\n\n// time\nexport * from './time/formatDate'\nexport * from './time/formatDateRelative'\nexport * from './time/time'\n\n// types\nexport type * from './types/NullToOptional'\nexport type * from './types/object'\nexport type * from './types/react'\nexport type * from './types/timer'\nexport type * from './types/tuple'\n\n// url\nexport * from './url/urlSanitize'\nexport * from './url/urlValidate'\n"
9
9
  ],
10
10
  "version": 3
11
11
  }
@@ -6,7 +6,7 @@ export declare function postfixObjKeys<
6
6
  B extends string
7
7
  >(obj: A, postfix: B): { [Key in `${keyof A extends string ? keyof A : never}${B}`] : string };
8
8
  export declare function objectFromEntries<ARR_T extends EntriesType>(arr: ARR_T): EntriesToObject<ARR_T>;
9
- export declare function objectKeys<O extends Object>(obj: O): Array<keyof O>;
9
+ export declare function objectKeys<O extends object>(obj: O): Array<keyof O>;
10
10
  export declare function objectEntries<OBJ_T extends ObjectType>(obj: OBJ_T): ObjectEntries<OBJ_T>;
11
11
 
12
12
  //# sourceMappingURL=object.d.ts.map
@@ -1,5 +1,5 @@
1
1
  {
2
- "mappings": "AAAA,cACE,iBACA,aACA,eACA,kBACK,iBAAiB;AAExB,OAAO,iBAAS;CAAe,UAAU;;CAAyB;CAAE;EAClEA,KAAK,GACLC,SAAS,OAER,gBAAgB,yBAAyB,YAAY;AAOxD,OAAO,iBAAS,kBAAkB,cAAc,aAC9CC,KAAK,QACJ,gBAAgB;AAInB,OAAO,iBAAS,WAAW,UAAU,QAAQC,KAAK,IACrB,YAAY;AAGzC,OAAO,iBAAS,cAAc,cAAc,YAC1CC,KAAK,QACJ,cAAc",
2
+ "mappings": "AAAA,cACE,iBACA,aACA,eACA,kBACK,iBAAiB;AAExB,OAAO,iBAAS;CAAe,UAAU;;CAAyB;CAAE;EAClEA,KAAK,GACLC,SAAS,OAER,gBAAgB,yBAAyB,YAAY;AAOxD,OAAO,iBAAS,kBAAkB,cAAc,aAC9CC,KAAK,QACJ,gBAAgB;AAInB,OAAO,iBAAS,WAAW,kBAAkBC,KAAK,IACrB,YAAY;AAGzC,OAAO,iBAAS,cAAc,cAAc,YAC1CC,KAAK,QACJ,cAAc",
3
3
  "names": [
4
4
  "obj: A",
5
5
  "postfix: B",
@@ -11,7 +11,7 @@
11
11
  "src/object/object.ts"
12
12
  ],
13
13
  "sourcesContent": [
14
- "import type {\n EntriesToObject,\n EntriesType,\n ObjectEntries,\n ObjectType,\n} from '../types/object'\n\nexport function postfixObjKeys<A extends { [key: string]: string }, B extends string>(\n obj: A,\n postfix: B\n): {\n [Key in `${keyof A extends string ? keyof A : never}${B}`]: string\n} {\n return Object.fromEntries(\n Object.entries(obj).map(([k, v]) => [`${k}${postfix}`, v])\n ) as any\n}\n\nexport function objectFromEntries<ARR_T extends EntriesType>(\n arr: ARR_T\n): EntriesToObject<ARR_T> {\n return Object.fromEntries(arr) as EntriesToObject<ARR_T>\n}\n\nexport function objectKeys<O extends Object>(obj: O) {\n return Object.keys(obj) as Array<keyof O>\n}\n\nexport function objectEntries<OBJ_T extends ObjectType>(\n obj: OBJ_T\n): ObjectEntries<OBJ_T> {\n return Object.entries(obj) as ObjectEntries<OBJ_T>\n}\n"
14
+ "import type {\n EntriesToObject,\n EntriesType,\n ObjectEntries,\n ObjectType,\n} from '../types/object'\n\nexport function postfixObjKeys<A extends { [key: string]: string }, B extends string>(\n obj: A,\n postfix: B\n): {\n [Key in `${keyof A extends string ? keyof A : never}${B}`]: string\n} {\n return Object.fromEntries(\n Object.entries(obj).map(([k, v]) => [`${k}${postfix}`, v])\n ) as any\n}\n\nexport function objectFromEntries<ARR_T extends EntriesType>(\n arr: ARR_T\n): EntriesToObject<ARR_T> {\n return Object.fromEntries(arr) as EntriesToObject<ARR_T>\n}\n\nexport function objectKeys<O extends object>(obj: O) {\n return Object.keys(obj) as Array<keyof O>\n}\n\nexport function objectEntries<OBJ_T extends ObjectType>(\n obj: OBJ_T\n): ObjectEntries<OBJ_T> {\n return Object.entries(obj) as ObjectEntries<OBJ_T>\n}\n"
15
15
  ],
16
16
  "version": 3
17
17
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "mappings": "AAAA,cAAc,eAA8B,OAAO;;;AAMnD,OAAO,iBAAS,oBAAoB,GAAGA,aAAaC,cAAc,IAAI,QAAQ",
2
+ "mappings": "AAAA,cAAc,eAA8B,OAAO;;;AAOnD,OAAO,iBAAS,oBAAoB,GAAGA,aAAaC,cAAc,IAAI,QAAQ",
3
3
  "names": [
4
4
  "key: string",
5
5
  "defaultValue: T"
@@ -8,7 +8,7 @@
8
8
  "src/react/createGlobalContext.ts"
9
9
  ],
10
10
  "sourcesContent": [
11
- "import { type Context, createContext } from 'react'\nimport { globalValue } from '../global/globalValue'\n\n// create or retrieve a React context that is stored on `globalThis`.\n// this ensures a stable singleton that survives hot-reloads during development.\n\nexport function createGlobalContext<T>(key: string, defaultValue: T): Context<T> {\n return globalValue(key, () => createContext<T>(defaultValue))\n}\n"
11
+ "import { type Context, createContext } from 'react'\n\nimport { globalValue } from '../global/globalValue'\n\n// create or retrieve a React context that is stored on `globalThis`.\n// this ensures a stable singleton that survives hot-reloads during development.\n\nexport function createGlobalContext<T>(key: string, defaultValue: T): Context<T> {\n return globalValue(key, () => createContext<T>(defaultValue))\n}\n"
12
12
  ],
13
13
  "version": 3
14
14
  }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * namespaced storage interface with JSON serialization
3
+ * @template K - key type (string literal union for type-safe keys)
4
+ * @template V - value type (automatically JSON serialized/deserialized)
5
+ */
6
+ export interface Storage<
7
+ K extends string = string,
8
+ V = unknown
9
+ > {
10
+ /** get a JSON-parsed value by key */
11
+ get(key: K): V | undefined;
12
+ /** set a value (JSON serialized) */
13
+ set(key: K, value: V): void;
14
+ /** remove a key */
15
+ remove(key: K): void;
16
+ /** check if key exists */
17
+ has(key: K): boolean;
18
+ /** get all keys in this namespace */
19
+ keys(): K[];
20
+ /** remove all keys in this namespace */
21
+ clear(): void;
22
+ /** get raw string value (no JSON parsing) - localStorage compatible */
23
+ getItem(key: K): string | null;
24
+ /** set raw string value (no JSON serialization) - localStorage compatible */
25
+ setItem(key: K, value: string): void;
26
+ }
27
+ /**
28
+ * create a namespaced storage instance
29
+ * @param namespace - unique prefix for all keys (throws if already used)
30
+ * @returns storage instance with get/set (JSON) and getItem/setItem (raw) methods
31
+ * @example
32
+ * const store = createStorage<'token' | 'user', string>('auth')
33
+ * store.set('token', 'abc123')
34
+ * store.get('token') // 'abc123'
35
+ */
36
+ export declare function createStorage<
37
+ K extends string,
38
+ V
39
+ >(namespace: string): Storage<K, V>;
40
+ /**
41
+ * single-value storage interface
42
+ * @template T - value type (automatically JSON serialized/deserialized)
43
+ */
44
+ export interface StorageValue<T> {
45
+ /** get the stored value */
46
+ get(): T | undefined;
47
+ /** set the value */
48
+ set(value: T): void;
49
+ /** remove the value */
50
+ remove(): void;
51
+ /** check if value exists */
52
+ has(): boolean;
53
+ }
54
+ /**
55
+ * create a single-value storage (wrapper around createStorage)
56
+ * @param key - unique storage key
57
+ * @returns storage value instance
58
+ * @example
59
+ * const token = createStorageValue<string>('auth-token')
60
+ * token.set('abc123')
61
+ * token.get() // 'abc123'
62
+ */
63
+ export declare function createStorageValue<T>(key: string): StorageValue<T>;
64
+
65
+ //# sourceMappingURL=createStorage.d.ts.map
@@ -0,0 +1,18 @@
1
+ {
2
+ "mappings": ";;;;;AASA,iBAAiB;CAAQ;CAA2B;EAAa;;CAE/D,IAAIA,KAAK,IAAI;;CAEb,IAAIA,KAAK,GAAGC,OAAO;;CAEnB,OAAOD,KAAK;;CAEZ,IAAIA,KAAK;;CAET,QAAQ;;CAER;;CAEA,QAAQA,KAAK;;CAEb,QAAQA,KAAK,GAAGE;AACjB;;;;;;;;;;AAWD,OAAO,iBAAS;CAAc;CAAkB;EAAGC,oBAAoB,QAAQ,GAAG;;;;;AA2ElF,iBAAiB,aAAa,GAAG;;CAE/B,OAAO;;CAEP,IAAIC,OAAO;;CAEX;;CAEA;AACD;;;;;;;;;;AAWD,OAAO,iBAAS,mBAAmB,GAAGC,cAAc,aAAa",
3
+ "names": [
4
+ "key: K",
5
+ "value: V",
6
+ "value: string",
7
+ "namespace: string",
8
+ "value: T",
9
+ "key: string"
10
+ ],
11
+ "sources": [
12
+ "src/storage/createStorage.ts"
13
+ ],
14
+ "sourcesContent": [
15
+ "import { getStorageDriver } from './driver'\n\nconst namespaces = new Set<string>()\n\n/**\n * namespaced storage interface with JSON serialization\n * @template K - key type (string literal union for type-safe keys)\n * @template V - value type (automatically JSON serialized/deserialized)\n */\nexport interface Storage<K extends string = string, V = unknown> {\n /** get a JSON-parsed value by key */\n get(key: K): V | undefined\n /** set a value (JSON serialized) */\n set(key: K, value: V): void\n /** remove a key */\n remove(key: K): void\n /** check if key exists */\n has(key: K): boolean\n /** get all keys in this namespace */\n keys(): K[]\n /** remove all keys in this namespace */\n clear(): void\n /** get raw string value (no JSON parsing) - localStorage compatible */\n getItem(key: K): string | null\n /** set raw string value (no JSON serialization) - localStorage compatible */\n setItem(key: K, value: string): void\n}\n\n/**\n * create a namespaced storage instance\n * @param namespace - unique prefix for all keys (throws if already used)\n * @returns storage instance with get/set (JSON) and getItem/setItem (raw) methods\n * @example\n * const store = createStorage<'token' | 'user', string>('auth')\n * store.set('token', 'abc123')\n * store.get('token') // 'abc123'\n */\nexport function createStorage<K extends string, V>(namespace: string): Storage<K, V> {\n if (namespaces.has(namespace)) {\n throw new Error(`storage namespace already exists: ${namespace}`)\n }\n namespaces.add(namespace)\n\n const prefix = `${namespace}:`\n const prefixKey = (key: string) => `${prefix}${key}`\n\n return {\n get(key: K): V | undefined {\n const driver = getStorageDriver()\n if (!driver) return undefined\n const raw = driver.getItem(prefixKey(key))\n if (raw == null) return undefined\n try {\n return JSON.parse(raw)\n } catch {\n return undefined\n }\n },\n\n set(key: K, value: V): void {\n const driver = getStorageDriver()\n if (!driver) return\n driver.setItem(prefixKey(key), JSON.stringify(value))\n },\n\n remove(key: K): void {\n const driver = getStorageDriver()\n if (!driver) return\n driver.removeItem(prefixKey(key))\n },\n\n has(key: K): boolean {\n const driver = getStorageDriver()\n if (!driver) return false\n return driver.getItem(prefixKey(key)) != null\n },\n\n keys(): K[] {\n const driver = getStorageDriver()\n if (!driver) return []\n return driver\n .getAllKeys()\n .filter((k) => k.startsWith(prefix))\n .map((k) => k.slice(prefix.length) as K)\n },\n\n clear(): void {\n const driver = getStorageDriver()\n if (!driver) return\n for (const key of this.keys()) {\n driver.removeItem(prefixKey(key))\n }\n },\n\n getItem(key: K): string | null {\n const driver = getStorageDriver()\n if (!driver) return null\n return driver.getItem(prefixKey(key)) ?? null\n },\n\n setItem(key: K, value: string): void {\n const driver = getStorageDriver()\n if (!driver) return\n driver.setItem(prefixKey(key), value)\n },\n }\n}\n\n/**\n * single-value storage interface\n * @template T - value type (automatically JSON serialized/deserialized)\n */\nexport interface StorageValue<T> {\n /** get the stored value */\n get(): T | undefined\n /** set the value */\n set(value: T): void\n /** remove the value */\n remove(): void\n /** check if value exists */\n has(): boolean\n}\n\n/**\n * create a single-value storage (wrapper around createStorage)\n * @param key - unique storage key\n * @returns storage value instance\n * @example\n * const token = createStorageValue<string>('auth-token')\n * token.set('abc123')\n * token.get() // 'abc123'\n */\nexport function createStorageValue<T>(key: string): StorageValue<T> {\n const storage = createStorage<'value', T>(`_v:${key}`)\n return {\n get: (): T | undefined => storage.get('value'),\n set: (value: T): void => storage.set('value', value),\n remove: (): void => storage.remove('value'),\n has: (): boolean => storage.has('value'),\n }\n}\n"
16
+ ],
17
+ "version": 3
18
+ }
@@ -0,0 +1,5 @@
1
+ import type { StorageDriver } from "./types";
2
+ export declare function setStorageDriver(d: StorageDriver): void;
3
+ export declare function getStorageDriver(): StorageDriver | null;
4
+
5
+ //# sourceMappingURL=driver.d.ts.map
@@ -0,0 +1,13 @@
1
+ {
2
+ "mappings": "AAAA,cAAc,qBAAqB,SAAS;AAI5C,OAAO,iBAAS,iBAAiBA,GAAG;AAIpC,OAAO,iBAAS,oBAAoB",
3
+ "names": [
4
+ "d: StorageDriver"
5
+ ],
6
+ "sources": [
7
+ "src/storage/driver.ts"
8
+ ],
9
+ "sourcesContent": [
10
+ "import type { StorageDriver } from './types'\n\nlet driver: StorageDriver | null = null\n\nexport function setStorageDriver(d: StorageDriver): void {\n driver = d\n}\n\nexport function getStorageDriver(): StorageDriver | null {\n if (driver) return driver\n if (typeof localStorage !== 'undefined') {\n return {\n getItem: (key) => localStorage.getItem(key),\n setItem: (key, value) => localStorage.setItem(key, value),\n removeItem: (key) => localStorage.removeItem(key),\n getAllKeys: () => Object.keys(localStorage),\n }\n }\n return null\n}\n"
11
+ ],
12
+ "version": 3
13
+ }
@@ -0,0 +1,6 @@
1
+ export { createStorage, createStorageValue } from "./createStorage";
2
+ export type { Storage, StorageValue } from "./createStorage";
3
+ export { getStorageDriver, setStorageDriver } from "./driver";
4
+ export type { StorageDriver } from "./types";
5
+
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "mappings": "AAAA,SAAS,eAAe,0BAA0B;AAClD,cAAc,SAAS,oBAAoB;AAC3C,SAAS,kBAAkB,wBAAwB;AACnD,cAAc,qBAAqB",
3
+ "names": [],
4
+ "sources": [
5
+ "src/storage/index.ts"
6
+ ],
7
+ "sourcesContent": [
8
+ "export { createStorage, createStorageValue } from './createStorage'\nexport type { Storage, StorageValue } from './createStorage'\nexport { getStorageDriver, setStorageDriver } from './driver'\nexport type { StorageDriver } from './types'\n"
9
+ ],
10
+ "version": 3
11
+ }
@@ -0,0 +1,8 @@
1
+ export interface StorageDriver {
2
+ getItem(key: string): string | null | undefined;
3
+ setItem(key: string, value: string): void;
4
+ removeItem(key: string): void;
5
+ getAllKeys(): string[];
6
+ }
7
+
8
+ //# sourceMappingURL=types.d.ts.map