@elurjs/core 3.5.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.
Files changed (191) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -0
  3. package/dist/lib/async.cjs +16 -0
  4. package/dist/lib/async.js +123 -0
  5. package/dist/lib/component.cjs +2 -0
  6. package/dist/lib/component.js +66 -0
  7. package/dist/lib/context.cjs +2 -0
  8. package/dist/lib/context.js +52 -0
  9. package/dist/lib/devtools.cjs +59 -0
  10. package/dist/lib/devtools.js +419 -0
  11. package/dist/lib/elur/async.cjs +16 -0
  12. package/dist/lib/elur/async.cjs.map +1 -0
  13. package/dist/lib/elur/async.d.cts +32 -0
  14. package/dist/lib/elur/async.d.ts +32 -0
  15. package/dist/lib/elur/async.js +123 -0
  16. package/dist/lib/elur/async.js.map +1 -0
  17. package/dist/lib/elur/component.cjs +2 -0
  18. package/dist/lib/elur/component.cjs.map +1 -0
  19. package/dist/lib/elur/component.d.cts +17 -0
  20. package/dist/lib/elur/component.d.ts +17 -0
  21. package/dist/lib/elur/component.js +66 -0
  22. package/dist/lib/elur/component.js.map +1 -0
  23. package/dist/lib/elur/context.cjs +2 -0
  24. package/dist/lib/elur/context.cjs.map +1 -0
  25. package/dist/lib/elur/context.d.cts +29 -0
  26. package/dist/lib/elur/context.d.ts +29 -0
  27. package/dist/lib/elur/context.js +52 -0
  28. package/dist/lib/elur/context.js.map +1 -0
  29. package/dist/lib/elur/devtools.cjs +59 -0
  30. package/dist/lib/elur/devtools.cjs.map +1 -0
  31. package/dist/lib/elur/devtools.d.cts +24 -0
  32. package/dist/lib/elur/devtools.d.ts +24 -0
  33. package/dist/lib/elur/devtools.js +419 -0
  34. package/dist/lib/elur/devtools.js.map +1 -0
  35. package/dist/lib/elur/form.cjs +2 -0
  36. package/dist/lib/elur/form.cjs.map +1 -0
  37. package/dist/lib/elur/form.d.cts +283 -0
  38. package/dist/lib/elur/form.d.ts +283 -0
  39. package/dist/lib/elur/form.js +315 -0
  40. package/dist/lib/elur/form.js.map +1 -0
  41. package/dist/lib/elur/hydrate/index.cjs +2 -0
  42. package/dist/lib/elur/hydrate/index.cjs.map +1 -0
  43. package/dist/lib/elur/hydrate/index.d.cts +13 -0
  44. package/dist/lib/elur/hydrate/index.d.ts +13 -0
  45. package/dist/lib/elur/hydrate/index.js +410 -0
  46. package/dist/lib/elur/hydrate/index.js.map +1 -0
  47. package/dist/lib/elur/index.d.cts +19 -0
  48. package/dist/lib/elur/index.d.ts +19 -0
  49. package/dist/lib/elur/lifecycle.cjs +2 -0
  50. package/dist/lib/elur/lifecycle.cjs.map +1 -0
  51. package/dist/lib/elur/lifecycle.d.cts +45 -0
  52. package/dist/lib/elur/lifecycle.d.ts +45 -0
  53. package/dist/lib/elur/lifecycle.js +39 -0
  54. package/dist/lib/elur/lifecycle.js.map +1 -0
  55. package/dist/lib/elur/plugins.cjs +2 -0
  56. package/dist/lib/elur/plugins.cjs.map +1 -0
  57. package/dist/lib/elur/plugins.d.cts +48 -0
  58. package/dist/lib/elur/plugins.d.ts +48 -0
  59. package/dist/lib/elur/plugins.js +59 -0
  60. package/dist/lib/elur/plugins.js.map +1 -0
  61. package/dist/lib/elur/reactivity.cjs +2 -0
  62. package/dist/lib/elur/reactivity.cjs.map +1 -0
  63. package/dist/lib/elur/reactivity.d.cts +60 -0
  64. package/dist/lib/elur/reactivity.d.ts +60 -0
  65. package/dist/lib/elur/reactivity.js +188 -0
  66. package/dist/lib/elur/reactivity.js.map +1 -0
  67. package/dist/lib/elur/router.cjs +12 -0
  68. package/dist/lib/elur/router.cjs.map +1 -0
  69. package/dist/lib/elur/router.d.cts +137 -0
  70. package/dist/lib/elur/router.d.ts +137 -0
  71. package/dist/lib/elur/router.js +513 -0
  72. package/dist/lib/elur/router.js.map +1 -0
  73. package/dist/lib/elur/server/index.cjs +2 -0
  74. package/dist/lib/elur/server/index.cjs.map +1 -0
  75. package/dist/lib/elur/server/index.d.cts +44 -0
  76. package/dist/lib/elur/server/index.d.ts +44 -0
  77. package/dist/lib/elur/server/index.js +307 -0
  78. package/dist/lib/elur/server/index.js.map +1 -0
  79. package/dist/lib/elur/store.cjs +2 -0
  80. package/dist/lib/elur/store.cjs.map +1 -0
  81. package/dist/lib/elur/store.d.cts +137 -0
  82. package/dist/lib/elur/store.d.ts +137 -0
  83. package/dist/lib/elur/store.js +143 -0
  84. package/dist/lib/elur/store.js.map +1 -0
  85. package/dist/lib/elur/template/bindings.cjs +2 -0
  86. package/dist/lib/elur/template/bindings.cjs.map +1 -0
  87. package/dist/lib/elur/template/bindings.d.cts +40 -0
  88. package/dist/lib/elur/template/bindings.d.ts +40 -0
  89. package/dist/lib/elur/template/bindings.js +214 -0
  90. package/dist/lib/elur/template/bindings.js.map +1 -0
  91. package/dist/lib/elur/template/dom-write.cjs +2 -0
  92. package/dist/lib/elur/template/dom-write.cjs.map +1 -0
  93. package/dist/lib/elur/template/dom-write.d.cts +5 -0
  94. package/dist/lib/elur/template/dom-write.d.ts +5 -0
  95. package/dist/lib/elur/template/dom-write.js +16 -0
  96. package/dist/lib/elur/template/dom-write.js.map +1 -0
  97. package/dist/lib/elur/template/error-boundary.cjs +2 -0
  98. package/dist/lib/elur/template/error-boundary.cjs.map +1 -0
  99. package/dist/lib/elur/template/error-boundary.d.cts +7 -0
  100. package/dist/lib/elur/template/error-boundary.d.ts +7 -0
  101. package/dist/lib/elur/template/error-boundary.js +90 -0
  102. package/dist/lib/elur/template/error-boundary.js.map +1 -0
  103. package/dist/lib/elur/template/html.cjs +2 -0
  104. package/dist/lib/elur/template/html.cjs.map +1 -0
  105. package/dist/lib/elur/template/html.d.cts +8 -0
  106. package/dist/lib/elur/template/html.d.ts +8 -0
  107. package/dist/lib/elur/template/html.js +104 -0
  108. package/dist/lib/elur/template/html.js.map +1 -0
  109. package/dist/lib/elur/template/index.cjs +1 -0
  110. package/dist/lib/elur/template/index.d.cts +15 -0
  111. package/dist/lib/elur/template/index.d.ts +15 -0
  112. package/dist/lib/elur/template/index.js +13 -0
  113. package/dist/lib/elur/template/keyed-diff.cjs +2 -0
  114. package/dist/lib/elur/template/keyed-diff.cjs.map +1 -0
  115. package/dist/lib/elur/template/keyed-diff.d.cts +32 -0
  116. package/dist/lib/elur/template/keyed-diff.d.ts +32 -0
  117. package/dist/lib/elur/template/keyed-diff.js +100 -0
  118. package/dist/lib/elur/template/keyed-diff.js.map +1 -0
  119. package/dist/lib/elur/template/keyed.cjs +2 -0
  120. package/dist/lib/elur/template/keyed.cjs.map +1 -0
  121. package/dist/lib/elur/template/keyed.d.cts +27 -0
  122. package/dist/lib/elur/template/keyed.d.ts +27 -0
  123. package/dist/lib/elur/template/keyed.js +52 -0
  124. package/dist/lib/elur/template/keyed.js.map +1 -0
  125. package/dist/lib/elur/template/mount-helpers.cjs +2 -0
  126. package/dist/lib/elur/template/mount-helpers.cjs.map +1 -0
  127. package/dist/lib/elur/template/mount-helpers.d.cts +29 -0
  128. package/dist/lib/elur/template/mount-helpers.d.ts +29 -0
  129. package/dist/lib/elur/template/mount-helpers.js +132 -0
  130. package/dist/lib/elur/template/mount-helpers.js.map +1 -0
  131. package/dist/lib/elur/template/node-binding.cjs +2 -0
  132. package/dist/lib/elur/template/node-binding.cjs.map +1 -0
  133. package/dist/lib/elur/template/node-binding.d.cts +6 -0
  134. package/dist/lib/elur/template/node-binding.d.ts +6 -0
  135. package/dist/lib/elur/template/node-binding.js +95 -0
  136. package/dist/lib/elur/template/node-binding.js.map +1 -0
  137. package/dist/lib/elur/template/portal.cjs +2 -0
  138. package/dist/lib/elur/template/portal.cjs.map +1 -0
  139. package/dist/lib/elur/template/portal.d.cts +19 -0
  140. package/dist/lib/elur/template/portal.d.ts +19 -0
  141. package/dist/lib/elur/template/portal.js +49 -0
  142. package/dist/lib/elur/template/portal.js.map +1 -0
  143. package/dist/lib/elur/template/raw.cjs +2 -0
  144. package/dist/lib/elur/template/raw.cjs.map +1 -0
  145. package/dist/lib/elur/template/raw.d.cts +9 -0
  146. package/dist/lib/elur/template/raw.d.ts +9 -0
  147. package/dist/lib/elur/template/raw.js +29 -0
  148. package/dist/lib/elur/template/raw.js.map +1 -0
  149. package/dist/lib/elur/template/sanitize.cjs +2 -0
  150. package/dist/lib/elur/template/sanitize.cjs.map +1 -0
  151. package/dist/lib/elur/template/sanitize.d.cts +16 -0
  152. package/dist/lib/elur/template/sanitize.d.ts +16 -0
  153. package/dist/lib/elur/template/sanitize.js +29 -0
  154. package/dist/lib/elur/template/sanitize.js.map +1 -0
  155. package/dist/lib/elur/template/transitions.cjs +2 -0
  156. package/dist/lib/elur/template/transitions.cjs.map +1 -0
  157. package/dist/lib/elur/template/transitions.d.cts +43 -0
  158. package/dist/lib/elur/template/transitions.d.ts +43 -0
  159. package/dist/lib/elur/template/transitions.js +96 -0
  160. package/dist/lib/elur/template/transitions.js.map +1 -0
  161. package/dist/lib/elur/template/types.cjs +2 -0
  162. package/dist/lib/elur/template/types.cjs.map +1 -0
  163. package/dist/lib/elur/template/types.d.cts +114 -0
  164. package/dist/lib/elur/template/types.d.ts +114 -0
  165. package/dist/lib/elur/template/types.js +23 -0
  166. package/dist/lib/elur/template/types.js.map +1 -0
  167. package/dist/lib/elur.cjs +1 -0
  168. package/dist/lib/elur.js +18 -0
  169. package/dist/lib/form.cjs +2 -0
  170. package/dist/lib/form.js +315 -0
  171. package/dist/lib/hydrate.cjs +2 -0
  172. package/dist/lib/hydrate.js +410 -0
  173. package/dist/lib/index.cjs +1 -0
  174. package/dist/lib/index.d.cts +2 -0
  175. package/dist/lib/index.d.ts +2 -0
  176. package/dist/lib/index.js +18 -0
  177. package/dist/lib/lifecycle.cjs +2 -0
  178. package/dist/lib/lifecycle.js +39 -0
  179. package/dist/lib/plugins.cjs +2 -0
  180. package/dist/lib/plugins.js +59 -0
  181. package/dist/lib/router.cjs +12 -0
  182. package/dist/lib/router.js +513 -0
  183. package/dist/lib/server.cjs +2 -0
  184. package/dist/lib/server.js +307 -0
  185. package/dist/lib/signals.cjs +2 -0
  186. package/dist/lib/signals.js +188 -0
  187. package/dist/lib/store.cjs +2 -0
  188. package/dist/lib/store.js +143 -0
  189. package/dist/lib/template.cjs +1 -0
  190. package/dist/lib/template.js +13 -0
  191. package/package.json +216 -0
@@ -0,0 +1,39 @@
1
+ //#region src/elur/lifecycle.ts
2
+ var e = class {
3
+ __isElurComponent = !0;
4
+ children;
5
+ _debugName;
6
+ _slots = /* @__PURE__ */ new Map();
7
+ setChildren(e) {
8
+ return this.children = e, this;
9
+ }
10
+ setSlot(e, t) {
11
+ return this._slots.set(e, t), this;
12
+ }
13
+ slot(e) {
14
+ return this._slots.get(e);
15
+ }
16
+ setDebugName(e) {
17
+ return this._debugName = e, this;
18
+ }
19
+ };
20
+ function t(e) {
21
+ return typeof e == "object" && !!e && e.__isElurComponent === !0;
22
+ }
23
+ var n = null;
24
+ function r(e) {
25
+ n = e;
26
+ }
27
+ function i(e) {
28
+ n?.onMountStart?.(e);
29
+ }
30
+ function a(e) {
31
+ n?.onMountEnd?.(e);
32
+ }
33
+ function o(e) {
34
+ n?.onUnmount?.(e);
35
+ }
36
+ //#endregion
37
+ export { e as ElurComponent, a as _debugComponentMountEnd, i as _debugComponentMountStart, o as _debugComponentUnmount, r as _setComponentDebugHooks, t as isElurComponent };
38
+
39
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","names":[],"sources":["../../src/elur/lifecycle.ts"],"sourcesContent":["import type { ElurTemplate } from \"./template/index.js\";\n\n// --- ElurChildren ---\n\n/** Valid child content for components. */\nexport type ElurChildren =\n | ElurTemplate\n | ElurComponent\n | Array<ElurTemplate | ElurComponent>\n | null\n | undefined;\n\n// --- ElurComponent ---\n\n/** Base class for components with lifecycle hooks. */\nexport abstract class ElurComponent {\n /** @internal */\n readonly __isElurComponent = true as const;\n\n /** Default slot — child content injected by the parent. */\n children?: ElurChildren;\n\n /** Optional label used by devtools. Falls back to class name. */\n _debugName?: string;\n\n /** @internal */\n private _slots = new Map<string, ElurChildren>();\n\n /** Sets the default slot content. Returns `this` for chaining. */\n setChildren(content: ElurChildren): this {\n this.children = content;\n return this;\n }\n\n /** Sets a named slot. Returns `this` for chaining. */\n setSlot(name: string, content: ElurChildren): this {\n this._slots.set(name, content);\n return this;\n }\n\n /** Returns content for a named slot. */\n slot(name: string): ElurChildren {\n return this._slots.get(name);\n }\n\n /** Sets an explicit devtools display name. Returns `this` for chaining. */\n setDebugName(name: string): this {\n this._debugName = name;\n return this;\n }\n\n /** Returns the component template. Called once on mount; updates happen via signals. */\n abstract render(): ElurTemplate;\n\n /** Called before `render()` — no DOM yet. Errors are caught by `onError` if present. */\n onInit?(): void;\n\n /** Server-only lifecycle hook. Runs during SSR after `onInit()`; never on the client. */\n onServerRender?(): void;\n\n /** Called after DOM insertion. May return a cleanup function. */\n onMount?(): (() => void) | void;\n\n /** Called before DOM removal. */\n onUnmount?(): void;\n\n /** Catches errors thrown in `onInit` and `onMount`. */\n onError?(err: unknown): void;\n}\n\n// --- Type guard ---\n\n/** @internal */\nexport function isElurComponent(v: unknown): v is ElurComponent {\n return (\n v != null &&\n typeof v === \"object\" &&\n (v as Record<string, unknown>).__isElurComponent === true\n );\n}\n\n// --- Devtools component tracking (internal) ---\n\nexport interface _ComponentDebugHooks {\n onMountStart?: (inst: ElurComponent) => void;\n onMountEnd?: (inst: ElurComponent) => void;\n onUnmount?: (inst: ElurComponent) => void;\n}\n\nlet _componentDebugHooks: _ComponentDebugHooks | null = null;\n\nexport function _setComponentDebugHooks(hooks: _ComponentDebugHooks | null): void {\n _componentDebugHooks = hooks;\n}\n\nexport function _debugComponentMountStart(inst: ElurComponent): void {\n _componentDebugHooks?.onMountStart?.(inst);\n}\n\nexport function _debugComponentMountEnd(inst: ElurComponent): void {\n _componentDebugHooks?.onMountEnd?.(inst);\n}\n\nexport function _debugComponentUnmount(inst: ElurComponent): void {\n _componentDebugHooks?.onUnmount?.(inst);\n}\n"],"mappings":";AAeA,IAAsB,IAAtB,MAAoC;CAEhC,oBAA6B;CAG7B;CAGA;CAGA,yBAAiB,IAAI,KAA2B;CAGhD,YAAY,GAA6B;AAErC,SADA,KAAK,WAAW,GACT;;CAIX,QAAQ,GAAc,GAA6B;AAE/C,SADA,KAAK,OAAO,IAAI,GAAM,EAAQ,EACvB;;CAIX,KAAK,GAA4B;AAC7B,SAAO,KAAK,OAAO,IAAI,EAAK;;CAIhC,aAAa,GAAoB;AAE7B,SADA,KAAK,aAAa,GACX;;;AAyBf,SAAgB,EAAgB,GAAgC;AAC5D,QAEI,OAAO,KAAM,cADb,KAEC,EAA8B,sBAAsB;;AAY7D,IAAI,IAAoD;AAExD,SAAgB,EAAwB,GAA0C;AAC9E,KAAuB;;AAG3B,SAAgB,EAA0B,GAA2B;AACjE,IAAsB,eAAe,EAAK;;AAG9C,SAAgB,EAAwB,GAA2B;AAC/D,IAAsB,aAAa,EAAK;;AAG5C,SAAgB,EAAuB,GAA2B;AAC9D,IAAsB,YAAY,EAAK"}
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./signals.cjs`);function t(t,n={}){let{storage:r=localStorage,exclude:i=[],serialize:a=JSON.stringify,deserialize:o=JSON.parse,debounce:s=0}=n;return n=>{e.untrack(async()=>{try{let e=await r.getItem(t);if(!e)return;let a=o(e),s={};for(let e of Object.keys(a))e in n.$state&&!i.includes(e)&&(s[e]=a[e]);Object.keys(s).length>0&&n.$patch(s)}catch{}});let c;return e.watch(n.$stateSignal,e=>{let n=()=>{try{let n=i.length===0?e:Object.fromEntries(Object.entries(e).filter(([e])=>!i.includes(e)));r.setItem(t,a(n))}catch{}};s>0?(clearTimeout(c),c=setTimeout(n,s)):n()})}}function n(t={}){let{collapsed:n=!0,filter:r}=t;return t=>{let i=n?console.groupCollapsed:console.group;return e.watch(t.$stateSignal,(e,n)=>{if(!n)return;let a={};for(let t of Object.keys(e))Object.is(e[t],n[t])||(a[t]=e[t]);Object.keys(a).length!==0&&(r&&!r(a)||(i(`%c[elur:${t.$id}]%c ${Object.keys(a).join(`, `)}`,`color:#7F77DD;font-weight:500`,`color:inherit;font-weight:400`),console.log(`prev →`,n),console.log(`next →`,e),console.log(`diff →`,a),console.groupEnd()))},{immediate:!0})}}function r(e){return t=>{let n=t._guardFns;for(let t of e)n.push(t);return()=>{for(let t of e){let e=n.indexOf(t);e>=0&&n.splice(e,1)}}}}function i(t,n){return r=>e.watch(t.$stateSignal,e=>{n(e,r)})}exports.bridgePlugin=i,exports.guardPlugin=r,exports.loggerPlugin=n,exports.persistPlugin=t;
2
+ //# sourceMappingURL=plugins.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugins.cjs","names":[],"sources":["../../src/elur/plugins.ts"],"sourcesContent":["import { watch, untrack } from \"./reactivity.js\";\nimport { type ElurPlugin, type Store, type GuardFn } from \"./store.js\";\n\n/**\n * Minimal interface that any storage adapter must implement.\n * Compatible with localStorage, sessionStorage, AsyncStorage, IndexedDB, etc.\n */\ninterface StorageAdapter {\n /** Retrieves an item from storage. Returns null if not found. */\n getItem(key: string): string | null | Promise<string | null>;\n /** Stores an item in storage. */\n setItem(key: string, value: string): void | Promise<void>;\n /** Optional: Removes an item from storage. */\n removeItem?(key: string): void | Promise<void>;\n}\n\n/**\n * Persists the store state to a storage medium (defaults to localStorage).\n * Automatically hydrates on initialization and saves changes on every reactive flush.\n */\nexport function persistPlugin<T extends Record<string, unknown>>(\n storageKey: string,\n opts: {\n /** Storage adapter to use. Defaults to localStorage. */\n storage?: StorageAdapter;\n /** Properties to exclude from persistence. */\n exclude?: Array<keyof T>;\n /** Custom serialization function. Defaults to JSON.stringify. */\n serialize?: (state: T) => string;\n /** Custom deserialization function. Defaults to JSON.parse. */\n deserialize?: (raw: string) => Partial<T>;\n /** Debounce interval in milliseconds for batching writes. Defaults to 0 (immediate). */\n debounce?: number;\n } = {},\n): ElurPlugin<T> {\n const {\n storage = localStorage,\n exclude = [],\n serialize = JSON.stringify,\n deserialize = JSON.parse,\n debounce = 0,\n } = opts;\n\n return (store) => {\n untrack(async () => {\n try {\n const raw = await storage.getItem(storageKey);\n if (!raw) return;\n const saved = deserialize(raw) as Partial<T>;\n\n const patch: Partial<T> = {};\n for (const key of Object.keys(saved)) {\n if (key in store.$state && !exclude.includes(key as keyof T)) {\n (patch as Record<string, unknown>)[key] = (saved as Record<string, unknown>)[key];\n }\n }\n if (Object.keys(patch).length > 0) store.$patch(patch);\n } catch {\n // Silently fail if storage is inaccessible (e.g., SSR or Private Mode).\n }\n });\n\n let timeoutId: ReturnType<typeof setTimeout>;\n\n return watch(store.$stateSignal, (next) => {\n const persist = () => {\n try {\n const toSave = exclude.length === 0\n ? next\n : Object.fromEntries(\n Object.entries(next).filter(([k]) => !exclude.includes(k as keyof T))\n ) as T;\n storage.setItem(storageKey, serialize(toSave));\n } catch {\n // Fail silently if storage quota is exceeded.\n }\n };\n\n if (debounce > 0) {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(persist, debounce);\n } else {\n persist();\n }\n });\n };\n}\n\n/**\n * Logs state transitions to the console.\n * Calculates property-level diffs and groups output by store ID.\n */\nexport function loggerPlugin<T extends Record<string, unknown>>(\n opts: {\n /** Whether the console group should be collapsed by default. */\n collapsed?: boolean;\n /** Optional filter to skip logging for specific changes. */\n filter?: (diff: Partial<T>) => boolean;\n } = {},\n): ElurPlugin<T> {\n const { collapsed = true, filter } = opts;\n\n return (store) => {\n const groupFn = collapsed ? console.groupCollapsed : console.group;\n\n return watch(store.$stateSignal, (next, prev) => {\n if (!prev) return;\n\n const diff: Partial<T> = {};\n for (const key of Object.keys(next) as Array<keyof T & string>) {\n if (!Object.is(next[key], prev[key])) {\n (diff as Record<string, unknown>)[key] = next[key];\n }\n }\n\n if (Object.keys(diff).length === 0) return;\n if (filter && !filter(diff)) return;\n\n groupFn(\n `%c[elur:${store.$id}]%c ${Object.keys(diff).join(\", \")}`,\n \"color:#7F77DD;font-weight:500\",\n \"color:inherit;font-weight:400\",\n );\n console.log(\"prev →\", prev);\n console.log(\"next →\", next);\n console.log(\"diff →\", diff);\n console.groupEnd();\n }, { immediate: true });\n };\n}\n\n/**\n * Intercepts $patch and $reset calls to validate or transform state before it is applied.\n */\nexport function guardPlugin<T extends Record<string, unknown>>(\n guards: GuardFn<T>[],\n): ElurPlugin<T> {\n return (store) => {\n const registry = (store as unknown as { _guardFns: GuardFn<T>[] })._guardFns;\n for (const guard of guards) {\n registry.push(guard);\n }\n return () => {\n for (const guard of guards) {\n const idx = registry.indexOf(guard);\n if (idx >= 0) registry.splice(idx, 1);\n }\n };\n };\n}\n\n/**\n * Synchronizes data between two stores by watching one and patching the other.\n */\nexport function bridgePlugin<\n TA extends Record<string, unknown>,\n TB extends Record<string, unknown>,\n>(\n sourceStore: Store<TB>,\n sync: (sourceState: TB, targetStore: Store<TA>) => void,\n): ElurPlugin<TA> {\n return (targetStore) => {\n return watch(sourceStore.$stateSignal, (sourceState) => {\n sync(sourceState, targetStore);\n });\n };\n}"],"mappings":"oGAoBA,SAAgB,EACZ,EACA,EAWI,EAAE,CACO,CACb,GAAM,CACF,UAAU,aACV,UAAU,EAAE,CACZ,YAAY,KAAK,UACjB,cAAc,KAAK,MACnB,WAAW,GACX,EAEJ,MAAQ,IAAU,CACd,EAAA,QAAQ,SAAY,CAChB,GAAI,CACA,IAAM,EAAM,MAAM,EAAQ,QAAQ,EAAW,CAC7C,GAAI,CAAC,EAAK,OACV,IAAM,EAAQ,EAAY,EAAI,CAExB,EAAoB,EAAE,CAC5B,IAAK,IAAM,KAAO,OAAO,KAAK,EAAM,CAC5B,KAAO,EAAM,QAAU,CAAC,EAAQ,SAAS,EAAe,GACvD,EAAkC,GAAQ,EAAkC,IAGjF,OAAO,KAAK,EAAM,CAAC,OAAS,GAAG,EAAM,OAAO,EAAM,MAClD,IAGV,CAEF,IAAI,EAEJ,OAAO,EAAA,MAAM,EAAM,aAAe,GAAS,CACvC,IAAM,MAAgB,CAClB,GAAI,CACA,IAAM,EAAS,EAAQ,SAAW,EAC5B,EACA,OAAO,YACL,OAAO,QAAQ,EAAK,CAAC,QAAQ,CAAC,KAAO,CAAC,EAAQ,SAAS,EAAa,CAAC,CACxE,CACL,EAAQ,QAAQ,EAAY,EAAU,EAAO,CAAC,MAC1C,IAKR,EAAW,GACX,aAAa,EAAU,CACvB,EAAY,WAAW,EAAS,EAAS,EAEzC,GAAS,EAEf,EAQV,SAAgB,EACZ,EAKI,EAAE,CACO,CACb,GAAM,CAAE,YAAY,GAAM,UAAW,EAErC,MAAQ,IAAU,CACd,IAAM,EAAU,EAAY,QAAQ,eAAiB,QAAQ,MAE7D,OAAO,EAAA,MAAM,EAAM,cAAe,EAAM,IAAS,CAC7C,GAAI,CAAC,EAAM,OAEX,IAAM,EAAmB,EAAE,CAC3B,IAAK,IAAM,KAAO,OAAO,KAAK,EAAK,CAC1B,OAAO,GAAG,EAAK,GAAM,EAAK,GAAK,GAC/B,EAAiC,GAAO,EAAK,IAIlD,OAAO,KAAK,EAAK,CAAC,SAAW,IAC7B,GAAU,CAAC,EAAO,EAAK,GAE3B,EACI,WAAW,EAAM,IAAI,MAAM,OAAO,KAAK,EAAK,CAAC,KAAK,KAAK,GACvD,gCACA,gCACH,CACD,QAAQ,IAAI,UAAW,EAAK,CAC5B,QAAQ,IAAI,UAAW,EAAK,CAC5B,QAAQ,IAAI,UAAW,EAAK,CAC5B,QAAQ,UAAU,IACnB,CAAE,UAAW,GAAM,CAAC,EAO/B,SAAgB,EACZ,EACa,CACb,MAAQ,IAAU,CACd,IAAM,EAAY,EAAiD,UACnE,IAAK,IAAM,KAAS,EAChB,EAAS,KAAK,EAAM,CAExB,UAAa,CACT,IAAK,IAAM,KAAS,EAAQ,CACxB,IAAM,EAAM,EAAS,QAAQ,EAAM,CAC/B,GAAO,GAAG,EAAS,OAAO,EAAK,EAAE,IASrD,SAAgB,EAIZ,EACA,EACc,CACd,MAAQ,IACG,EAAA,MAAM,EAAY,aAAe,GAAgB,CACpD,EAAK,EAAa,EAAY,EAChC"}
@@ -0,0 +1,48 @@
1
+ import { type ElurPlugin, type Store, type GuardFn } from "./store.js";
2
+ /**
3
+ * Minimal interface that any storage adapter must implement.
4
+ * Compatible with localStorage, sessionStorage, AsyncStorage, IndexedDB, etc.
5
+ */
6
+ interface StorageAdapter {
7
+ /** Retrieves an item from storage. Returns null if not found. */
8
+ getItem(key: string): string | null | Promise<string | null>;
9
+ /** Stores an item in storage. */
10
+ setItem(key: string, value: string): void | Promise<void>;
11
+ /** Optional: Removes an item from storage. */
12
+ removeItem?(key: string): void | Promise<void>;
13
+ }
14
+ /**
15
+ * Persists the store state to a storage medium (defaults to localStorage).
16
+ * Automatically hydrates on initialization and saves changes on every reactive flush.
17
+ */
18
+ export declare function persistPlugin<T extends Record<string, unknown>>(storageKey: string, opts?: {
19
+ /** Storage adapter to use. Defaults to localStorage. */
20
+ storage?: StorageAdapter;
21
+ /** Properties to exclude from persistence. */
22
+ exclude?: Array<keyof T>;
23
+ /** Custom serialization function. Defaults to JSON.stringify. */
24
+ serialize?: (state: T) => string;
25
+ /** Custom deserialization function. Defaults to JSON.parse. */
26
+ deserialize?: (raw: string) => Partial<T>;
27
+ /** Debounce interval in milliseconds for batching writes. Defaults to 0 (immediate). */
28
+ debounce?: number;
29
+ }): ElurPlugin<T>;
30
+ /**
31
+ * Logs state transitions to the console.
32
+ * Calculates property-level diffs and groups output by store ID.
33
+ */
34
+ export declare function loggerPlugin<T extends Record<string, unknown>>(opts?: {
35
+ /** Whether the console group should be collapsed by default. */
36
+ collapsed?: boolean;
37
+ /** Optional filter to skip logging for specific changes. */
38
+ filter?: (diff: Partial<T>) => boolean;
39
+ }): ElurPlugin<T>;
40
+ /**
41
+ * Intercepts $patch and $reset calls to validate or transform state before it is applied.
42
+ */
43
+ export declare function guardPlugin<T extends Record<string, unknown>>(guards: GuardFn<T>[]): ElurPlugin<T>;
44
+ /**
45
+ * Synchronizes data between two stores by watching one and patching the other.
46
+ */
47
+ export declare function bridgePlugin<TA extends Record<string, unknown>, TB extends Record<string, unknown>>(sourceStore: Store<TB>, sync: (sourceState: TB, targetStore: Store<TA>) => void): ElurPlugin<TA>;
48
+ export {};
@@ -0,0 +1,48 @@
1
+ import { type ElurPlugin, type Store, type GuardFn } from "./store.js";
2
+ /**
3
+ * Minimal interface that any storage adapter must implement.
4
+ * Compatible with localStorage, sessionStorage, AsyncStorage, IndexedDB, etc.
5
+ */
6
+ interface StorageAdapter {
7
+ /** Retrieves an item from storage. Returns null if not found. */
8
+ getItem(key: string): string | null | Promise<string | null>;
9
+ /** Stores an item in storage. */
10
+ setItem(key: string, value: string): void | Promise<void>;
11
+ /** Optional: Removes an item from storage. */
12
+ removeItem?(key: string): void | Promise<void>;
13
+ }
14
+ /**
15
+ * Persists the store state to a storage medium (defaults to localStorage).
16
+ * Automatically hydrates on initialization and saves changes on every reactive flush.
17
+ */
18
+ export declare function persistPlugin<T extends Record<string, unknown>>(storageKey: string, opts?: {
19
+ /** Storage adapter to use. Defaults to localStorage. */
20
+ storage?: StorageAdapter;
21
+ /** Properties to exclude from persistence. */
22
+ exclude?: Array<keyof T>;
23
+ /** Custom serialization function. Defaults to JSON.stringify. */
24
+ serialize?: (state: T) => string;
25
+ /** Custom deserialization function. Defaults to JSON.parse. */
26
+ deserialize?: (raw: string) => Partial<T>;
27
+ /** Debounce interval in milliseconds for batching writes. Defaults to 0 (immediate). */
28
+ debounce?: number;
29
+ }): ElurPlugin<T>;
30
+ /**
31
+ * Logs state transitions to the console.
32
+ * Calculates property-level diffs and groups output by store ID.
33
+ */
34
+ export declare function loggerPlugin<T extends Record<string, unknown>>(opts?: {
35
+ /** Whether the console group should be collapsed by default. */
36
+ collapsed?: boolean;
37
+ /** Optional filter to skip logging for specific changes. */
38
+ filter?: (diff: Partial<T>) => boolean;
39
+ }): ElurPlugin<T>;
40
+ /**
41
+ * Intercepts $patch and $reset calls to validate or transform state before it is applied.
42
+ */
43
+ export declare function guardPlugin<T extends Record<string, unknown>>(guards: GuardFn<T>[]): ElurPlugin<T>;
44
+ /**
45
+ * Synchronizes data between two stores by watching one and patching the other.
46
+ */
47
+ export declare function bridgePlugin<TA extends Record<string, unknown>, TB extends Record<string, unknown>>(sourceStore: Store<TB>, sync: (sourceState: TB, targetStore: Store<TA>) => void): ElurPlugin<TA>;
48
+ export {};
@@ -0,0 +1,59 @@
1
+ import { untrack as e, watch as t } from "./signals.js";
2
+ //#region src/elur/plugins.ts
3
+ function n(n, r = {}) {
4
+ let { storage: i = localStorage, exclude: a = [], serialize: o = JSON.stringify, deserialize: s = JSON.parse, debounce: c = 0 } = r;
5
+ return (r) => {
6
+ e(async () => {
7
+ try {
8
+ let e = await i.getItem(n);
9
+ if (!e) return;
10
+ let t = s(e), o = {};
11
+ for (let e of Object.keys(t)) e in r.$state && !a.includes(e) && (o[e] = t[e]);
12
+ Object.keys(o).length > 0 && r.$patch(o);
13
+ } catch {}
14
+ });
15
+ let l;
16
+ return t(r.$stateSignal, (e) => {
17
+ let t = () => {
18
+ try {
19
+ let t = a.length === 0 ? e : Object.fromEntries(Object.entries(e).filter(([e]) => !a.includes(e)));
20
+ i.setItem(n, o(t));
21
+ } catch {}
22
+ };
23
+ c > 0 ? (clearTimeout(l), l = setTimeout(t, c)) : t();
24
+ });
25
+ };
26
+ }
27
+ function r(e = {}) {
28
+ let { collapsed: n = !0, filter: r } = e;
29
+ return (e) => {
30
+ let i = n ? console.groupCollapsed : console.group;
31
+ return t(e.$stateSignal, (t, n) => {
32
+ if (!n) return;
33
+ let a = {};
34
+ for (let e of Object.keys(t)) Object.is(t[e], n[e]) || (a[e] = t[e]);
35
+ Object.keys(a).length !== 0 && (r && !r(a) || (i(`%c[elur:${e.$id}]%c ${Object.keys(a).join(", ")}`, "color:#7F77DD;font-weight:500", "color:inherit;font-weight:400"), console.log("prev →", n), console.log("next →", t), console.log("diff →", a), console.groupEnd()));
36
+ }, { immediate: !0 });
37
+ };
38
+ }
39
+ function i(e) {
40
+ return (t) => {
41
+ let n = t._guardFns;
42
+ for (let t of e) n.push(t);
43
+ return () => {
44
+ for (let t of e) {
45
+ let e = n.indexOf(t);
46
+ e >= 0 && n.splice(e, 1);
47
+ }
48
+ };
49
+ };
50
+ }
51
+ function a(e, n) {
52
+ return (r) => t(e.$stateSignal, (e) => {
53
+ n(e, r);
54
+ });
55
+ }
56
+ //#endregion
57
+ export { a as bridgePlugin, i as guardPlugin, r as loggerPlugin, n as persistPlugin };
58
+
59
+ //# sourceMappingURL=plugins.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugins.js","names":[],"sources":["../../src/elur/plugins.ts"],"sourcesContent":["import { watch, untrack } from \"./reactivity.js\";\nimport { type ElurPlugin, type Store, type GuardFn } from \"./store.js\";\n\n/**\n * Minimal interface that any storage adapter must implement.\n * Compatible with localStorage, sessionStorage, AsyncStorage, IndexedDB, etc.\n */\ninterface StorageAdapter {\n /** Retrieves an item from storage. Returns null if not found. */\n getItem(key: string): string | null | Promise<string | null>;\n /** Stores an item in storage. */\n setItem(key: string, value: string): void | Promise<void>;\n /** Optional: Removes an item from storage. */\n removeItem?(key: string): void | Promise<void>;\n}\n\n/**\n * Persists the store state to a storage medium (defaults to localStorage).\n * Automatically hydrates on initialization and saves changes on every reactive flush.\n */\nexport function persistPlugin<T extends Record<string, unknown>>(\n storageKey: string,\n opts: {\n /** Storage adapter to use. Defaults to localStorage. */\n storage?: StorageAdapter;\n /** Properties to exclude from persistence. */\n exclude?: Array<keyof T>;\n /** Custom serialization function. Defaults to JSON.stringify. */\n serialize?: (state: T) => string;\n /** Custom deserialization function. Defaults to JSON.parse. */\n deserialize?: (raw: string) => Partial<T>;\n /** Debounce interval in milliseconds for batching writes. Defaults to 0 (immediate). */\n debounce?: number;\n } = {},\n): ElurPlugin<T> {\n const {\n storage = localStorage,\n exclude = [],\n serialize = JSON.stringify,\n deserialize = JSON.parse,\n debounce = 0,\n } = opts;\n\n return (store) => {\n untrack(async () => {\n try {\n const raw = await storage.getItem(storageKey);\n if (!raw) return;\n const saved = deserialize(raw) as Partial<T>;\n\n const patch: Partial<T> = {};\n for (const key of Object.keys(saved)) {\n if (key in store.$state && !exclude.includes(key as keyof T)) {\n (patch as Record<string, unknown>)[key] = (saved as Record<string, unknown>)[key];\n }\n }\n if (Object.keys(patch).length > 0) store.$patch(patch);\n } catch {\n // Silently fail if storage is inaccessible (e.g., SSR or Private Mode).\n }\n });\n\n let timeoutId: ReturnType<typeof setTimeout>;\n\n return watch(store.$stateSignal, (next) => {\n const persist = () => {\n try {\n const toSave = exclude.length === 0\n ? next\n : Object.fromEntries(\n Object.entries(next).filter(([k]) => !exclude.includes(k as keyof T))\n ) as T;\n storage.setItem(storageKey, serialize(toSave));\n } catch {\n // Fail silently if storage quota is exceeded.\n }\n };\n\n if (debounce > 0) {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(persist, debounce);\n } else {\n persist();\n }\n });\n };\n}\n\n/**\n * Logs state transitions to the console.\n * Calculates property-level diffs and groups output by store ID.\n */\nexport function loggerPlugin<T extends Record<string, unknown>>(\n opts: {\n /** Whether the console group should be collapsed by default. */\n collapsed?: boolean;\n /** Optional filter to skip logging for specific changes. */\n filter?: (diff: Partial<T>) => boolean;\n } = {},\n): ElurPlugin<T> {\n const { collapsed = true, filter } = opts;\n\n return (store) => {\n const groupFn = collapsed ? console.groupCollapsed : console.group;\n\n return watch(store.$stateSignal, (next, prev) => {\n if (!prev) return;\n\n const diff: Partial<T> = {};\n for (const key of Object.keys(next) as Array<keyof T & string>) {\n if (!Object.is(next[key], prev[key])) {\n (diff as Record<string, unknown>)[key] = next[key];\n }\n }\n\n if (Object.keys(diff).length === 0) return;\n if (filter && !filter(diff)) return;\n\n groupFn(\n `%c[elur:${store.$id}]%c ${Object.keys(diff).join(\", \")}`,\n \"color:#7F77DD;font-weight:500\",\n \"color:inherit;font-weight:400\",\n );\n console.log(\"prev →\", prev);\n console.log(\"next →\", next);\n console.log(\"diff →\", diff);\n console.groupEnd();\n }, { immediate: true });\n };\n}\n\n/**\n * Intercepts $patch and $reset calls to validate or transform state before it is applied.\n */\nexport function guardPlugin<T extends Record<string, unknown>>(\n guards: GuardFn<T>[],\n): ElurPlugin<T> {\n return (store) => {\n const registry = (store as unknown as { _guardFns: GuardFn<T>[] })._guardFns;\n for (const guard of guards) {\n registry.push(guard);\n }\n return () => {\n for (const guard of guards) {\n const idx = registry.indexOf(guard);\n if (idx >= 0) registry.splice(idx, 1);\n }\n };\n };\n}\n\n/**\n * Synchronizes data between two stores by watching one and patching the other.\n */\nexport function bridgePlugin<\n TA extends Record<string, unknown>,\n TB extends Record<string, unknown>,\n>(\n sourceStore: Store<TB>,\n sync: (sourceState: TB, targetStore: Store<TA>) => void,\n): ElurPlugin<TA> {\n return (targetStore) => {\n return watch(sourceStore.$stateSignal, (sourceState) => {\n sync(sourceState, targetStore);\n });\n };\n}"],"mappings":";;AAoBA,SAAgB,EACZ,GACA,IAWI,EAAE,EACO;CACb,IAAM,EACF,aAAU,cACV,aAAU,EAAE,EACZ,eAAY,KAAK,WACjB,iBAAc,KAAK,OACnB,cAAW,MACX;AAEJ,SAAQ,MAAU;AACd,IAAQ,YAAY;AAChB,OAAI;IACA,IAAM,IAAM,MAAM,EAAQ,QAAQ,EAAW;AAC7C,QAAI,CAAC,EAAK;IACV,IAAM,IAAQ,EAAY,EAAI,EAExB,IAAoB,EAAE;AAC5B,SAAK,IAAM,KAAO,OAAO,KAAK,EAAM,CAChC,CAAI,KAAO,EAAM,UAAU,CAAC,EAAQ,SAAS,EAAe,KACvD,EAAkC,KAAQ,EAAkC;AAGrF,IAAI,OAAO,KAAK,EAAM,CAAC,SAAS,KAAG,EAAM,OAAO,EAAM;WAClD;IAGV;EAEF,IAAI;AAEJ,SAAO,EAAM,EAAM,eAAe,MAAS;GACvC,IAAM,UAAgB;AAClB,QAAI;KACA,IAAM,IAAS,EAAQ,WAAW,IAC5B,IACA,OAAO,YACL,OAAO,QAAQ,EAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAQ,SAAS,EAAa,CAAC,CACxE;AACL,OAAQ,QAAQ,GAAY,EAAU,EAAO,CAAC;YAC1C;;AAKZ,GAAI,IAAW,KACX,aAAa,EAAU,EACvB,IAAY,WAAW,GAAS,EAAS,IAEzC,GAAS;IAEf;;;AAQV,SAAgB,EACZ,IAKI,EAAE,EACO;CACb,IAAM,EAAE,eAAY,IAAM,cAAW;AAErC,SAAQ,MAAU;EACd,IAAM,IAAU,IAAY,QAAQ,iBAAiB,QAAQ;AAE7D,SAAO,EAAM,EAAM,eAAe,GAAM,MAAS;AAC7C,OAAI,CAAC,EAAM;GAEX,IAAM,IAAmB,EAAE;AAC3B,QAAK,IAAM,KAAO,OAAO,KAAK,EAAK,CAC/B,CAAK,OAAO,GAAG,EAAK,IAAM,EAAK,GAAK,KAC/B,EAAiC,KAAO,EAAK;AAIlD,UAAO,KAAK,EAAK,CAAC,WAAW,MAC7B,KAAU,CAAC,EAAO,EAAK,KAE3B,EACI,WAAW,EAAM,IAAI,MAAM,OAAO,KAAK,EAAK,CAAC,KAAK,KAAK,IACvD,iCACA,gCACH,EACD,QAAQ,IAAI,WAAW,EAAK,EAC5B,QAAQ,IAAI,WAAW,EAAK,EAC5B,QAAQ,IAAI,WAAW,EAAK,EAC5B,QAAQ,UAAU;KACnB,EAAE,WAAW,IAAM,CAAC;;;AAO/B,SAAgB,EACZ,GACa;AACb,SAAQ,MAAU;EACd,IAAM,IAAY,EAAiD;AACnE,OAAK,IAAM,KAAS,EAChB,GAAS,KAAK,EAAM;AAExB,eAAa;AACT,QAAK,IAAM,KAAS,GAAQ;IACxB,IAAM,IAAM,EAAS,QAAQ,EAAM;AACnC,IAAI,KAAO,KAAG,EAAS,OAAO,GAAK,EAAE;;;;;AASrD,SAAgB,EAIZ,GACA,GACc;AACd,SAAQ,MACG,EAAM,EAAY,eAAe,MAAgB;AACpD,IAAK,GAAa,EAAY;GAChC"}
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(){return{ctxPool:[],ctxStack:[],activeEffect:null,activeDeps:null,activeErrorHandler:null,errorHandlerStack:[],batchLevel:0,pendingEffectsSet:new Set,pendingEffectsArr:[],effectDepth:0,notifyBuf:[],notifyBase:0,signalDebugHooks:null}}var t=Symbol.for(`elur/reactivity-state`),n=globalThis,r=(()=>{let r=n[t];if(r)return r;let i=e();return n[t]=i,i})();function i(e){r.errorHandlerStack.push(r.activeErrorHandler),r.activeErrorHandler=e}function a(){r.activeErrorHandler=r.errorHandlerStack.pop()??null}var o=100,s=64,c=16,l=32;function u(e){r.signalDebugHooks=e}function d(){return r.notifyBuf.length}var f=class{_value;_subs=new Set;constructor(e){this._value=e,r.signalDebugHooks?.onCreate?.(this,e)}get value(){return r.activeEffect&&(this._subs.add(r.activeEffect),r.activeDeps?.add(this)),this._value}set value(e){Object.is(this._value,e)||(this._value=e,r.signalDebugHooks?.onWrite?.(this,e),this._notify())}update(e){this.value=e(this._value)}peek(){return this._value}_removeSub(e){this._subs.delete(e)}_notify(){if(r.batchLevel>0){for(let e of this._subs)r.pendingEffectsSet.has(e)||(r.pendingEffectsSet.add(e),r.pendingEffectsArr.push(e));return}let e=r.notifyBase,t=0;for(let n of this._subs)r.notifyBuf[e+ t++]=n;r.notifyBase=e+t;try{for(let n=0;n<t;n++){let t=r.notifyBuf[e+n];r.notifyBuf[e+n]=null,t?.()}}finally{r.notifyBase=e,e===0&&r.notifyBuf.length>s&&t<c&&(r.notifyBuf.length=l)}}dispose(){this._subs.clear()}};function p(e){return new f(e)}function m(e){let t=!1,n,i=new Set,a=new Set,s=r.activeErrorHandler,c=()=>{if(t)return;typeof n==`function`&&n();let l=i;i=a,a=l,a.clear();let u=r.ctxPool.length>0?r.ctxPool.pop():{effect:null,deps:null};if(u.effect=r.activeEffect,u.deps=r.activeDeps,r.ctxStack.push(u),r.activeEffect=c,r.activeDeps=a,r.effectDepth++,r.effectDepth>o){r.effectDepth=0;let e=r.ctxStack.pop();throw r.activeEffect=e.effect,r.activeDeps=e.deps,e.effect=null,e.deps=null,r.ctxPool.push(e),Error(`[elur] Maximum effect re-execution depth exceeded (possible infinite loop).`)}try{n=e()}catch(e){if(s)s(e);else throw e}finally{r.effectDepth--;let e=r.ctxStack.pop();r.activeEffect=e.effect,r.activeDeps=e.deps,e.effect=null,e.deps=null,r.ctxPool.push(e);for(let e of i)a.has(e)||e._removeSub(c)}};return c(),()=>{t=!0,typeof n==`function`&&n();for(let e of a)e._removeSub(c);a.clear(),i.clear()}}var h=Symbol(`elur-computed-uninitialized`);function g(e,t=Object.is){let n=new f(h),r=null,i=!1,a=!1,o=()=>{i||a||(i=!0,v(()=>{r=m(()=>{let r=e(),i=n.peek();(i===h||!t(r,i))&&(n.value=r)})}))},s=Object.getOwnPropertyDescriptor(Object.getPrototypeOf(n),`value`);if(!s?.get||!s?.set)throw Error(`[elur] Internal error: Signal.value descriptor not found.`);Object.defineProperty(n,`value`,{get(){return o(),s.get.call(this)},set(e){s.set.call(this,e)},configurable:!0});let c=n.dispose;return n.dispose=()=>{a=!0,r?.(),r=null,c.call(n)},n}function _(e){r.batchLevel++;try{e()}finally{if(r.batchLevel--,r.batchLevel===0&&r.pendingEffectsArr.length>0){let e=r.pendingEffectsArr.length;for(let t=0;t<e;t++)r.pendingEffectsArr[t]();r.pendingEffectsArr.length=0,r.pendingEffectsSet.clear()}}}function v(e){let t=r.activeEffect,n=r.activeDeps;r.activeEffect=null,r.activeDeps=null;try{return e()}finally{r.activeEffect=t,r.activeDeps=n}}function y(e,t,n={}){let{immediate:r=!1,once:i=!1}=n,a=e instanceof f?()=>e.value:e,o,s=!0,c=!1,l=m(()=>{let e=a();if(s){if(s=!1,r&&!c){let n=e;v(()=>t(n,void 0)),i&&(c=!0,Promise.resolve().then(l))}o=e;return}if(!c){let n=e,r=o;o=e,v(()=>t(n,r)),i&&(c=!0,Promise.resolve().then(l))}});return()=>{c=!0,l()}}function b(e){return e?Promise.resolve().then(e):Promise.resolve()}exports.Signal=f,exports._getNotifyBufSize=d,exports._popErrorHandler=a,exports._pushErrorHandler=i,exports._setSignalDebugHooks=u,exports.batch=_,exports.computed=g,exports.effect=m,exports.nextTick=b,exports.signal=p,exports.untrack=v,exports.watch=y;
2
+ //# sourceMappingURL=signals.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signals.cjs","names":[],"sources":["../../src/elur/reactivity.ts"],"sourcesContent":["// --- Dependency tracking ---\n\ninterface _EffectCtx {\n effect: (() => void) | null;\n deps: Set<Signal<any>> | null;\n}\n\ninterface _ReactivityGlobalState {\n ctxPool: _EffectCtx[];\n ctxStack: _EffectCtx[];\n activeEffect: (() => void) | null;\n activeDeps: Set<Signal<any>> | null;\n activeErrorHandler: ((err: unknown) => void) | null;\n errorHandlerStack: (((err: unknown) => void) | null)[];\n batchLevel: number;\n pendingEffectsSet: Set<() => void>;\n pendingEffectsArr: (() => void)[];\n effectDepth: number;\n notifyBuf: ((() => void) | null)[];\n notifyBase: number;\n signalDebugHooks: _SignalDebugHooks | null;\n}\n\nfunction _createReactivityState(): _ReactivityGlobalState {\n return {\n ctxPool: [],\n ctxStack: [],\n activeEffect: null,\n activeDeps: null,\n activeErrorHandler: null,\n errorHandlerStack: [],\n batchLevel: 0,\n pendingEffectsSet: new Set<() => void>(),\n pendingEffectsArr: [],\n effectDepth: 0,\n notifyBuf: [],\n notifyBase: 0,\n signalDebugHooks: null,\n };\n}\n\nconst _reactivityStateKey = Symbol.for(\"elur/reactivity-state\");\nconst _globalObj = globalThis as Record<PropertyKey, unknown>;\nconst _state = (() => {\n const existing = _globalObj[_reactivityStateKey] as _ReactivityGlobalState | undefined;\n if (existing) return existing;\n const created = _createReactivityState();\n _globalObj[_reactivityStateKey] = created;\n return created;\n})();\n\n// --- Error boundary support ---\n\n/**\n * @internal — Register an error boundary handler. All `effect()` calls made\n * synchronously while this handler is active will capture it. When those\n * effects re-run and throw, the captured handler is invoked.\n */\nexport function _pushErrorHandler(h: (err: unknown) => void): void {\n _state.errorHandlerStack.push(_state.activeErrorHandler);\n _state.activeErrorHandler = h;\n}\n\n/** @internal — Restore the previous error boundary handler. */\nexport function _popErrorHandler(): void {\n _state.activeErrorHandler = _state.errorHandlerStack.pop() ?? null;\n}\n\n// --- Batching ---\n\n/**\n * Guards against synchronous effect re-entrancy.\n *\n * Effects re-run synchronously when a signal they depend on changes. If an\n * effect writes to a signal it also reads, this creates a synchronous loop.\n * The guard below caps re-execution at MAX_EFFECT_DEPTH (100) iterations and\n * throws an error to prevent stack overflow.\n *\n * Throwing is deliberate: when the guard fires, the reactive graph is in an\n * inconsistent state — the effect partially executed, dependencies may be\n * stale, and continuing could produce incorrect UI. Throwing surfaces the\n * bug immediately instead of silently degrading.\n *\n * To avoid re-entrancy:\n * 1. Use `computed()` for derived state — computed signals cache their result\n * and only recompute when dependencies change. They never write to other\n * signals, so they can't loop.\n * 2. Use `watch()` for side effects — watch callbacks receive the new value;\n * if you need to update other signals, do it inside `batch()` to coalesce\n * notifications.\n * 3. Never write to a signal you read in the same effect — this is the direct\n * cause of re-entrancy. If you need to transform a value, use `computed()`\n * instead.\n * 4. Use `batch()` to group writes — batched writes flush once at the end,\n * preventing intermediate re-runs.\n */\n\nconst MAX_EFFECT_DEPTH = 100;\nconst _NOTIFY_SHRINK_TRIGGER = 64;\nconst _NOTIFY_LOW_USAGE = 16;\nconst _NOTIFY_SHRINK_TO = 32;\n\nexport interface _SignalDebugHooks {\n onCreate?: (signal: Signal<any>, initialValue: unknown) => void;\n onWrite?: (signal: Signal<any>, value: unknown) => void;\n}\n\nexport function _setSignalDebugHooks(hooks: _SignalDebugHooks | null): void {\n _state.signalDebugHooks = hooks;\n}\n\n/** @internal — notify buffer capacity, exposed for tests. */\nexport function _getNotifyBufSize(): number {\n return _state.notifyBuf.length;\n}\n\n// --- Signal ---\n\nexport class Signal<T> {\n private _value: T;\n private _subs = new Set<() => void>();\n\n constructor(initialValue: T) {\n this._value = initialValue;\n _state.signalDebugHooks?.onCreate?.(this, initialValue);\n }\n\n /** Read the current value. Subscribes the active effect if one exists. */\n get value(): T {\n if (_state.activeEffect) {\n this._subs.add(_state.activeEffect);\n _state.activeDeps?.add(this);\n }\n return this._value;\n }\n\n /** Write a new value. Notifies subscribers when the value changes. */\n set value(newValue: T) {\n if (Object.is(this._value, newValue)) return;\n this._value = newValue;\n _state.signalDebugHooks?.onWrite?.(this, newValue);\n this._notify();\n }\n\n /** Mutate the value via a updater function. */\n update(fn: (current: T) => T): void {\n this.value = fn(this._value);\n }\n\n /** Read without subscribing the active effect. */\n peek(): T {\n return this._value;\n }\n\n /** @internal */\n _removeSub(sub: () => void): void {\n this._subs.delete(sub);\n }\n\n private _notify(): void {\n if (_state.batchLevel > 0) {\n for (const s of this._subs) {\n if (!_state.pendingEffectsSet.has(s)) {\n _state.pendingEffectsSet.add(s);\n _state.pendingEffectsArr.push(s);\n }\n }\n return;\n }\n const base = _state.notifyBase;\n let len = 0;\n for (const s of this._subs) _state.notifyBuf[base + len++] = s;\n _state.notifyBase = base + len;\n try {\n for (let i = 0; i < len; i++) {\n const fn = _state.notifyBuf[base + i];\n _state.notifyBuf[base + i] = null!;\n fn?.();\n }\n } finally {\n _state.notifyBase = base;\n // Shrink oversized notify buffer after a low-usage top-level flush.\n if (\n base === 0 &&\n _state.notifyBuf.length > _NOTIFY_SHRINK_TRIGGER &&\n len < _NOTIFY_LOW_USAGE\n ) {\n _state.notifyBuf.length = _NOTIFY_SHRINK_TO;\n }\n }\n }\n\n dispose(): void {\n this._subs.clear();\n }\n}\n\n// --- Factories ---\n\nexport function signal<T>(initialValue: T): Signal<T> {\n return new Signal(initialValue);\n}\n\n/**\n * Runs `fn` and re-runs it whenever any signal read inside changes.\n * Returns a dispose function to tear down the effect.\n * If `fn` returns a function, it is called as cleanup before each re-run\n * and on disposal.\n */\nexport function effect(fn: () => void | (() => void)): () => void {\n let disposed = false;\n let cleanup: (() => void) | void;\n\n // Opt: Double buffering para evitar crear `new Set()` en cada ejecución\n let deps = new Set<Signal<any>>();\n let newDeps = new Set<Signal<any>>();\n\n const capturedErrorHandler = _state.activeErrorHandler;\n\n const execute = () => {\n if (disposed) return;\n if (typeof cleanup === \"function\") cleanup();\n\n // Intercambiamos los buffers. 'deps' ahora tiene los viejos, 'newDeps' está limpio para recolectar.\n const temp = deps;\n deps = newDeps;\n newDeps = temp;\n newDeps.clear();\n\n const ctx = _state.ctxPool.length > 0 ? _state.ctxPool.pop()! : { effect: null, deps: null };\n ctx.effect = _state.activeEffect;\n ctx.deps = _state.activeDeps;\n _state.ctxStack.push(ctx);\n _state.activeEffect = execute;\n _state.activeDeps = newDeps;\n\n _state.effectDepth++;\n if (_state.effectDepth > MAX_EFFECT_DEPTH) {\n _state.effectDepth = 0;\n // Restaurar desde el stack unificado\n const restored = _state.ctxStack.pop()!;\n _state.activeEffect = restored.effect;\n _state.activeDeps = restored.deps;\n restored.effect = null;\n restored.deps = null;\n _state.ctxPool.push(restored);\n throw new Error(\n \"[elur] Maximum effect re-execution depth exceeded (possible infinite loop).\"\n );\n }\n\n try {\n cleanup = fn();\n } catch (err) {\n if (capturedErrorHandler) {\n capturedErrorHandler(err);\n } else {\n throw err;\n }\n } finally {\n _state.effectDepth--;\n const restored = _state.ctxStack.pop()!;\n _state.activeEffect = restored.effect;\n _state.activeDeps = restored.deps;\n restored.effect = null; // limpiar referencias para GC\n restored.deps = null;\n _state.ctxPool.push(restored); // devolver al pool para reutilizar\n\n\n // Cleanup phase: Desuscribirse de señales que estaban en 'deps' pero NO en 'newDeps'\n for (const oldDep of deps) {\n if (!newDeps.has(oldDep)) {\n oldDep._removeSub(execute);\n }\n }\n }\n };\n\n execute();\n\n return () => {\n disposed = true;\n if (typeof cleanup === \"function\") cleanup();\n // Al desechar, usamos newDeps porque es el que quedó activo después del último execute\n for (const dep of newDeps) {\n dep._removeSub(execute);\n }\n newDeps.clear();\n deps.clear();\n };\n}\n\n/** Derived signal that recalculates when its dependencies change. */\nconst _computedSentinel = Symbol(\"elur-computed-uninitialized\");\n\nexport function computed<T>(\n fn: () => T,\n equals: (a: T, b: T) => boolean = Object.is,\n): Signal<T> & { dispose(): void } {\n const s = new Signal<T | typeof _computedSentinel>(_computedSentinel);\n let disposeEffect: (() => void) | null = null;\n let initialized = false;\n let disposed = false;\n\n const ensureInitialized = () => {\n if (initialized || disposed) return;\n initialized = true;\n // Important: initialize untracked so first computed evaluation does not\n // accidentally subscribe the currently running external effect.\n untrack(() => {\n disposeEffect = effect(() => {\n const next = fn();\n const prev = s.peek();\n // The first computed value must always be written; afterwards use\n // the supplied equality comparator (defaults to Object.is).\n if (prev === _computedSentinel || !equals(next, prev)) {\n s.value = next;\n }\n });\n });\n };\n\n const signalProto = Object.getPrototypeOf(s) as Signal<T>;\n const valueDescriptor = Object.getOwnPropertyDescriptor(signalProto, \"value\");\n if (!valueDescriptor?.get || !valueDescriptor?.set) {\n throw new Error(\"[elur] Internal error: Signal.value descriptor not found.\");\n }\n\n Object.defineProperty(s, \"value\", {\n get() {\n ensureInitialized();\n return valueDescriptor.get!.call(this) as T;\n },\n set(v: T) {\n valueDescriptor.set!.call(this, v);\n },\n configurable: true,\n });\n\n const originalDispose = s.dispose;\n\n s.dispose = () => {\n disposed = true;\n disposeEffect?.();\n disposeEffect = null;\n originalDispose.call(s); // Opt: Evitar el uso lento de .bind()\n };\n return s as Signal<T> & { dispose(): void };\n}\n\n/** Groups multiple signal writes so effects flush once at the end. */\nexport function batch(fn: () => void): void {\n _state.batchLevel++;\n try {\n fn();\n } finally {\n _state.batchLevel--;\n if (_state.batchLevel === 0 && _state.pendingEffectsArr.length > 0) {\n const len = _state.pendingEffectsArr.length;\n for (let i = 0; i < len; i++) _state.pendingEffectsArr[i]();\n _state.pendingEffectsArr.length = 0; // reset O(1) sin GC — key del cambio\n _state.pendingEffectsSet.clear();\n }\n }\n}\n\n/** Executes `fn` without subscribing to any signals read inside it. */\nexport function untrack<T>(fn: () => T): T {\n const prevEffect = _state.activeEffect;\n const prevDeps = _state.activeDeps;\n _state.activeEffect = null;\n _state.activeDeps = null;\n try {\n return fn();\n } finally {\n _state.activeEffect = prevEffect;\n _state.activeDeps = prevDeps;\n }\n}\n\n// --- watch ---\n\nexport interface WatchOptions {\n /** Fire the callback immediately with the current value. Default: `false`. */\n immediate?: boolean;\n /** Automatically dispose after the first callback invocation. Default: `false`. */\n once?: boolean;\n}\n\n/**\n * Watches a reactive source and calls `callback(newValue, oldValue)` on each change.\n * Accepts a Signal or a getter function. Returns a dispose function.\n */\nexport function watch<T>(\n source: Signal<T> | (() => T),\n callback: (newValue: T, oldValue: T | undefined) => void,\n options: WatchOptions = {}\n): () => void {\n const { immediate = false, once = false } = options;\n\n const getter: () => T =\n source instanceof Signal ? () => source.value : source;\n\n let oldValue: T | undefined;\n let isFirst = true;\n let disposed = false;\n\n const dispose = effect(() => {\n const newValue = getter();\n\n if (isFirst) {\n isFirst = false;\n if (immediate && !disposed) {\n const snap = newValue;\n untrack(() => callback(snap, undefined));\n if (once) { disposed = true; Promise.resolve().then(dispose); }\n }\n oldValue = newValue;\n return;\n }\n\n if (!disposed) {\n const snap = newValue;\n const prev = oldValue;\n oldValue = newValue;\n untrack(() => callback(snap, prev));\n if (once) { disposed = true; Promise.resolve().then(dispose); }\n }\n });\n\n return () => {\n disposed = true;\n dispose();\n };\n}\n\n// --- nextTick ---\n\n/** Returns a promise that resolves on the next microtask. Accepts an optional callback. */\nexport function nextTick(fn?: () => void): Promise<void> {\n return fn ? Promise.resolve().then(fn) : Promise.resolve();\n}"],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @internal — Register an error boundary handler. All `effect()` calls made
3
+ * synchronously while this handler is active will capture it. When those
4
+ * effects re-run and throw, the captured handler is invoked.
5
+ */
6
+ export declare function _pushErrorHandler(h: (err: unknown) => void): void;
7
+ /** @internal — Restore the previous error boundary handler. */
8
+ export declare function _popErrorHandler(): void;
9
+ export interface _SignalDebugHooks {
10
+ onCreate?: (signal: Signal<any>, initialValue: unknown) => void;
11
+ onWrite?: (signal: Signal<any>, value: unknown) => void;
12
+ }
13
+ export declare function _setSignalDebugHooks(hooks: _SignalDebugHooks | null): void;
14
+ /** @internal — notify buffer capacity, exposed for tests. */
15
+ export declare function _getNotifyBufSize(): number;
16
+ export declare class Signal<T> {
17
+ private _value;
18
+ private _subs;
19
+ constructor(initialValue: T);
20
+ /** Read the current value. Subscribes the active effect if one exists. */
21
+ get value(): T;
22
+ /** Write a new value. Notifies subscribers when the value changes. */
23
+ set value(newValue: T);
24
+ /** Mutate the value via a updater function. */
25
+ update(fn: (current: T) => T): void;
26
+ /** Read without subscribing the active effect. */
27
+ peek(): T;
28
+ /** @internal */
29
+ _removeSub(sub: () => void): void;
30
+ private _notify;
31
+ dispose(): void;
32
+ }
33
+ export declare function signal<T>(initialValue: T): Signal<T>;
34
+ /**
35
+ * Runs `fn` and re-runs it whenever any signal read inside changes.
36
+ * Returns a dispose function to tear down the effect.
37
+ * If `fn` returns a function, it is called as cleanup before each re-run
38
+ * and on disposal.
39
+ */
40
+ export declare function effect(fn: () => void | (() => void)): () => void;
41
+ export declare function computed<T>(fn: () => T, equals?: (a: T, b: T) => boolean): Signal<T> & {
42
+ dispose(): void;
43
+ };
44
+ /** Groups multiple signal writes so effects flush once at the end. */
45
+ export declare function batch(fn: () => void): void;
46
+ /** Executes `fn` without subscribing to any signals read inside it. */
47
+ export declare function untrack<T>(fn: () => T): T;
48
+ export interface WatchOptions {
49
+ /** Fire the callback immediately with the current value. Default: `false`. */
50
+ immediate?: boolean;
51
+ /** Automatically dispose after the first callback invocation. Default: `false`. */
52
+ once?: boolean;
53
+ }
54
+ /**
55
+ * Watches a reactive source and calls `callback(newValue, oldValue)` on each change.
56
+ * Accepts a Signal or a getter function. Returns a dispose function.
57
+ */
58
+ export declare function watch<T>(source: Signal<T> | (() => T), callback: (newValue: T, oldValue: T | undefined) => void, options?: WatchOptions): () => void;
59
+ /** Returns a promise that resolves on the next microtask. Accepts an optional callback. */
60
+ export declare function nextTick(fn?: () => void): Promise<void>;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @internal — Register an error boundary handler. All `effect()` calls made
3
+ * synchronously while this handler is active will capture it. When those
4
+ * effects re-run and throw, the captured handler is invoked.
5
+ */
6
+ export declare function _pushErrorHandler(h: (err: unknown) => void): void;
7
+ /** @internal — Restore the previous error boundary handler. */
8
+ export declare function _popErrorHandler(): void;
9
+ export interface _SignalDebugHooks {
10
+ onCreate?: (signal: Signal<any>, initialValue: unknown) => void;
11
+ onWrite?: (signal: Signal<any>, value: unknown) => void;
12
+ }
13
+ export declare function _setSignalDebugHooks(hooks: _SignalDebugHooks | null): void;
14
+ /** @internal — notify buffer capacity, exposed for tests. */
15
+ export declare function _getNotifyBufSize(): number;
16
+ export declare class Signal<T> {
17
+ private _value;
18
+ private _subs;
19
+ constructor(initialValue: T);
20
+ /** Read the current value. Subscribes the active effect if one exists. */
21
+ get value(): T;
22
+ /** Write a new value. Notifies subscribers when the value changes. */
23
+ set value(newValue: T);
24
+ /** Mutate the value via a updater function. */
25
+ update(fn: (current: T) => T): void;
26
+ /** Read without subscribing the active effect. */
27
+ peek(): T;
28
+ /** @internal */
29
+ _removeSub(sub: () => void): void;
30
+ private _notify;
31
+ dispose(): void;
32
+ }
33
+ export declare function signal<T>(initialValue: T): Signal<T>;
34
+ /**
35
+ * Runs `fn` and re-runs it whenever any signal read inside changes.
36
+ * Returns a dispose function to tear down the effect.
37
+ * If `fn` returns a function, it is called as cleanup before each re-run
38
+ * and on disposal.
39
+ */
40
+ export declare function effect(fn: () => void | (() => void)): () => void;
41
+ export declare function computed<T>(fn: () => T, equals?: (a: T, b: T) => boolean): Signal<T> & {
42
+ dispose(): void;
43
+ };
44
+ /** Groups multiple signal writes so effects flush once at the end. */
45
+ export declare function batch(fn: () => void): void;
46
+ /** Executes `fn` without subscribing to any signals read inside it. */
47
+ export declare function untrack<T>(fn: () => T): T;
48
+ export interface WatchOptions {
49
+ /** Fire the callback immediately with the current value. Default: `false`. */
50
+ immediate?: boolean;
51
+ /** Automatically dispose after the first callback invocation. Default: `false`. */
52
+ once?: boolean;
53
+ }
54
+ /**
55
+ * Watches a reactive source and calls `callback(newValue, oldValue)` on each change.
56
+ * Accepts a Signal or a getter function. Returns a dispose function.
57
+ */
58
+ export declare function watch<T>(source: Signal<T> | (() => T), callback: (newValue: T, oldValue: T | undefined) => void, options?: WatchOptions): () => void;
59
+ /** Returns a promise that resolves on the next microtask. Accepts an optional callback. */
60
+ export declare function nextTick(fn?: () => void): Promise<void>;
@@ -0,0 +1,188 @@
1
+ //#region src/elur/reactivity.ts
2
+ function e() {
3
+ return {
4
+ ctxPool: [],
5
+ ctxStack: [],
6
+ activeEffect: null,
7
+ activeDeps: null,
8
+ activeErrorHandler: null,
9
+ errorHandlerStack: [],
10
+ batchLevel: 0,
11
+ pendingEffectsSet: /* @__PURE__ */ new Set(),
12
+ pendingEffectsArr: [],
13
+ effectDepth: 0,
14
+ notifyBuf: [],
15
+ notifyBase: 0,
16
+ signalDebugHooks: null
17
+ };
18
+ }
19
+ var t = Symbol.for("elur/reactivity-state"), n = globalThis, r = (() => {
20
+ let r = n[t];
21
+ if (r) return r;
22
+ let i = e();
23
+ return n[t] = i, i;
24
+ })();
25
+ function i(e) {
26
+ r.errorHandlerStack.push(r.activeErrorHandler), r.activeErrorHandler = e;
27
+ }
28
+ function a() {
29
+ r.activeErrorHandler = r.errorHandlerStack.pop() ?? null;
30
+ }
31
+ var o = 100, s = 64, c = 16, l = 32;
32
+ function u(e) {
33
+ r.signalDebugHooks = e;
34
+ }
35
+ function d() {
36
+ return r.notifyBuf.length;
37
+ }
38
+ var f = class {
39
+ _value;
40
+ _subs = /* @__PURE__ */ new Set();
41
+ constructor(e) {
42
+ this._value = e, r.signalDebugHooks?.onCreate?.(this, e);
43
+ }
44
+ get value() {
45
+ return r.activeEffect && (this._subs.add(r.activeEffect), r.activeDeps?.add(this)), this._value;
46
+ }
47
+ set value(e) {
48
+ Object.is(this._value, e) || (this._value = e, r.signalDebugHooks?.onWrite?.(this, e), this._notify());
49
+ }
50
+ update(e) {
51
+ this.value = e(this._value);
52
+ }
53
+ peek() {
54
+ return this._value;
55
+ }
56
+ _removeSub(e) {
57
+ this._subs.delete(e);
58
+ }
59
+ _notify() {
60
+ if (r.batchLevel > 0) {
61
+ for (let e of this._subs) r.pendingEffectsSet.has(e) || (r.pendingEffectsSet.add(e), r.pendingEffectsArr.push(e));
62
+ return;
63
+ }
64
+ let e = r.notifyBase, t = 0;
65
+ for (let n of this._subs) r.notifyBuf[e + t++] = n;
66
+ r.notifyBase = e + t;
67
+ try {
68
+ for (let n = 0; n < t; n++) {
69
+ let t = r.notifyBuf[e + n];
70
+ r.notifyBuf[e + n] = null, t?.();
71
+ }
72
+ } finally {
73
+ r.notifyBase = e, e === 0 && r.notifyBuf.length > s && t < c && (r.notifyBuf.length = l);
74
+ }
75
+ }
76
+ dispose() {
77
+ this._subs.clear();
78
+ }
79
+ };
80
+ function p(e) {
81
+ return new f(e);
82
+ }
83
+ function m(e) {
84
+ let t = !1, n, i = /* @__PURE__ */ new Set(), a = /* @__PURE__ */ new Set(), s = r.activeErrorHandler, c = () => {
85
+ if (t) return;
86
+ typeof n == "function" && n();
87
+ let l = i;
88
+ i = a, a = l, a.clear();
89
+ let u = r.ctxPool.length > 0 ? r.ctxPool.pop() : {
90
+ effect: null,
91
+ deps: null
92
+ };
93
+ if (u.effect = r.activeEffect, u.deps = r.activeDeps, r.ctxStack.push(u), r.activeEffect = c, r.activeDeps = a, r.effectDepth++, r.effectDepth > o) {
94
+ r.effectDepth = 0;
95
+ let e = r.ctxStack.pop();
96
+ throw r.activeEffect = e.effect, r.activeDeps = e.deps, e.effect = null, e.deps = null, r.ctxPool.push(e), Error("[elur] Maximum effect re-execution depth exceeded (possible infinite loop).");
97
+ }
98
+ try {
99
+ n = e();
100
+ } catch (e) {
101
+ if (s) s(e);
102
+ else throw e;
103
+ } finally {
104
+ r.effectDepth--;
105
+ let e = r.ctxStack.pop();
106
+ r.activeEffect = e.effect, r.activeDeps = e.deps, e.effect = null, e.deps = null, r.ctxPool.push(e);
107
+ for (let e of i) a.has(e) || e._removeSub(c);
108
+ }
109
+ };
110
+ return c(), () => {
111
+ t = !0, typeof n == "function" && n();
112
+ for (let e of a) e._removeSub(c);
113
+ a.clear(), i.clear();
114
+ };
115
+ }
116
+ var h = Symbol("elur-computed-uninitialized");
117
+ function g(e, t = Object.is) {
118
+ let n = new f(h), r = null, i = !1, a = !1, o = () => {
119
+ i || a || (i = !0, v(() => {
120
+ r = m(() => {
121
+ let r = e(), i = n.peek();
122
+ (i === h || !t(r, i)) && (n.value = r);
123
+ });
124
+ }));
125
+ }, s = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(n), "value");
126
+ if (!s?.get || !s?.set) throw Error("[elur] Internal error: Signal.value descriptor not found.");
127
+ Object.defineProperty(n, "value", {
128
+ get() {
129
+ return o(), s.get.call(this);
130
+ },
131
+ set(e) {
132
+ s.set.call(this, e);
133
+ },
134
+ configurable: !0
135
+ });
136
+ let c = n.dispose;
137
+ return n.dispose = () => {
138
+ a = !0, r?.(), r = null, c.call(n);
139
+ }, n;
140
+ }
141
+ function _(e) {
142
+ r.batchLevel++;
143
+ try {
144
+ e();
145
+ } finally {
146
+ if (r.batchLevel--, r.batchLevel === 0 && r.pendingEffectsArr.length > 0) {
147
+ let e = r.pendingEffectsArr.length;
148
+ for (let t = 0; t < e; t++) r.pendingEffectsArr[t]();
149
+ r.pendingEffectsArr.length = 0, r.pendingEffectsSet.clear();
150
+ }
151
+ }
152
+ }
153
+ function v(e) {
154
+ let t = r.activeEffect, n = r.activeDeps;
155
+ r.activeEffect = null, r.activeDeps = null;
156
+ try {
157
+ return e();
158
+ } finally {
159
+ r.activeEffect = t, r.activeDeps = n;
160
+ }
161
+ }
162
+ function y(e, t, n = {}) {
163
+ let { immediate: r = !1, once: i = !1 } = n, a = e instanceof f ? () => e.value : e, o, s = !0, c = !1, l = m(() => {
164
+ let e = a();
165
+ if (s) {
166
+ if (s = !1, r && !c) {
167
+ let n = e;
168
+ v(() => t(n, void 0)), i && (c = !0, Promise.resolve().then(l));
169
+ }
170
+ o = e;
171
+ return;
172
+ }
173
+ if (!c) {
174
+ let n = e, r = o;
175
+ o = e, v(() => t(n, r)), i && (c = !0, Promise.resolve().then(l));
176
+ }
177
+ });
178
+ return () => {
179
+ c = !0, l();
180
+ };
181
+ }
182
+ function b(e) {
183
+ return e ? Promise.resolve().then(e) : Promise.resolve();
184
+ }
185
+ //#endregion
186
+ export { f as Signal, d as _getNotifyBufSize, a as _popErrorHandler, i as _pushErrorHandler, u as _setSignalDebugHooks, _ as batch, g as computed, m as effect, b as nextTick, p as signal, v as untrack, y as watch };
187
+
188
+ //# sourceMappingURL=signals.js.map