@elurjs/core 3.6.2 → 3.7.0-beta.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 (211) hide show
  1. package/dist/lib/async.cjs +1 -1
  2. package/dist/lib/async.js +1 -1
  3. package/dist/lib/component.cjs +1 -2
  4. package/dist/lib/component.js +2 -66
  5. package/dist/lib/context.cjs +1 -1
  6. package/dist/lib/context.js +35 -23
  7. package/dist/lib/devtools.cjs +1 -1
  8. package/dist/lib/devtools.js +1 -1
  9. package/dist/lib/elur/async.cjs +1 -1
  10. package/dist/lib/elur/async.cjs.map +1 -1
  11. package/dist/lib/elur/async.js +1 -1
  12. package/dist/lib/elur/component.cjs +1 -2
  13. package/dist/lib/elur/component.d.cts +1 -17
  14. package/dist/lib/elur/component.d.ts +1 -17
  15. package/dist/lib/elur/component.js +2 -66
  16. package/dist/lib/elur/context.cjs +1 -1
  17. package/dist/lib/elur/context.cjs.map +1 -1
  18. package/dist/lib/elur/context.d.cts +5 -0
  19. package/dist/lib/elur/context.d.ts +5 -0
  20. package/dist/lib/elur/context.js +35 -23
  21. package/dist/lib/elur/context.js.map +1 -1
  22. package/dist/lib/elur/devtools.cjs +1 -1
  23. package/dist/lib/elur/devtools.cjs.map +1 -1
  24. package/dist/lib/elur/devtools.js +1 -1
  25. package/dist/lib/elur/form.cjs +1 -1
  26. package/dist/lib/elur/form.cjs.map +1 -1
  27. package/dist/lib/elur/form.js +1 -1
  28. package/dist/lib/elur/hydrate/index.cjs +1 -2
  29. package/dist/lib/elur/hydrate/index.d.cts +1 -13
  30. package/dist/lib/elur/hydrate/index.d.ts +1 -13
  31. package/dist/lib/elur/hydrate/index.js +2 -410
  32. package/dist/lib/elur/lifecycle.cjs +1 -1
  33. package/dist/lib/elur/lifecycle.cjs.map +1 -1
  34. package/dist/lib/elur/lifecycle.d.cts +27 -0
  35. package/dist/lib/elur/lifecycle.d.ts +27 -0
  36. package/dist/lib/elur/lifecycle.js +4 -0
  37. package/dist/lib/elur/lifecycle.js.map +1 -1
  38. package/dist/lib/elur/next/component.d.cts +281 -0
  39. package/dist/lib/elur/next/component.d.ts +281 -0
  40. package/dist/lib/elur/next/dom-write.d.cts +18 -0
  41. package/dist/lib/elur/next/dom-write.d.ts +18 -0
  42. package/dist/lib/elur/next/hydrate.d.cts +14 -0
  43. package/dist/lib/elur/next/hydrate.d.ts +14 -0
  44. package/dist/lib/elur/next/keyed-diff.d.cts +46 -0
  45. package/dist/lib/elur/next/keyed-diff.d.ts +46 -0
  46. package/dist/lib/elur/next/mount-helpers.d.cts +25 -0
  47. package/dist/lib/elur/next/mount-helpers.d.ts +25 -0
  48. package/dist/lib/elur/next/mount.d.cts +29 -0
  49. package/dist/lib/elur/next/mount.d.ts +29 -0
  50. package/dist/lib/elur/next/node-binding.d.cts +6 -0
  51. package/dist/lib/elur/next/node-binding.d.ts +6 -0
  52. package/dist/lib/elur/next/reactivity.d.cts +193 -0
  53. package/dist/lib/elur/next/reactivity.d.ts +193 -0
  54. package/dist/lib/elur/next-2/component.cjs +2 -0
  55. package/dist/lib/elur/next-2/component.cjs.map +1 -0
  56. package/dist/lib/elur/next-2/component.d.cts +283 -0
  57. package/dist/lib/elur/next-2/component.d.ts +283 -0
  58. package/dist/lib/elur/next-2/component.js +340 -0
  59. package/dist/lib/elur/next-2/component.js.map +1 -0
  60. package/dist/lib/elur/next-2/dom-write.cjs +2 -0
  61. package/dist/lib/elur/next-2/dom-write.cjs.map +1 -0
  62. package/dist/lib/elur/next-2/dom-write.d.cts +18 -0
  63. package/dist/lib/elur/next-2/dom-write.d.ts +18 -0
  64. package/dist/lib/elur/next-2/dom-write.js +31 -0
  65. package/dist/lib/elur/next-2/dom-write.js.map +1 -0
  66. package/dist/lib/elur/next-2/hydrate.cjs +2 -0
  67. package/dist/lib/elur/next-2/hydrate.cjs.map +1 -0
  68. package/dist/lib/elur/next-2/hydrate.d.cts +30 -0
  69. package/dist/lib/elur/next-2/hydrate.d.ts +30 -0
  70. package/dist/lib/elur/next-2/hydrate.js +449 -0
  71. package/dist/lib/elur/next-2/hydrate.js.map +1 -0
  72. package/dist/lib/elur/next-2/keyed-diff.cjs +2 -0
  73. package/dist/lib/elur/next-2/keyed-diff.cjs.map +1 -0
  74. package/dist/lib/elur/next-2/keyed-diff.d.cts +79 -0
  75. package/dist/lib/elur/next-2/keyed-diff.d.ts +79 -0
  76. package/dist/lib/elur/next-2/keyed-diff.js +208 -0
  77. package/dist/lib/elur/next-2/keyed-diff.js.map +1 -0
  78. package/dist/lib/elur/next-2/mount-helpers.cjs +2 -0
  79. package/dist/lib/elur/next-2/mount-helpers.cjs.map +1 -0
  80. package/dist/lib/elur/next-2/mount-helpers.d.cts +25 -0
  81. package/dist/lib/elur/next-2/mount-helpers.d.ts +25 -0
  82. package/dist/lib/elur/next-2/mount-helpers.js +87 -0
  83. package/dist/lib/elur/next-2/mount-helpers.js.map +1 -0
  84. package/dist/lib/elur/next-2/mount.cjs +2 -0
  85. package/dist/lib/elur/next-2/mount.cjs.map +1 -0
  86. package/dist/lib/elur/next-2/mount.d.cts +29 -0
  87. package/dist/lib/elur/next-2/mount.d.ts +29 -0
  88. package/dist/lib/elur/next-2/mount.js +34 -0
  89. package/dist/lib/elur/next-2/mount.js.map +1 -0
  90. package/dist/lib/elur/next-2/node-binding.cjs +2 -0
  91. package/dist/lib/elur/next-2/node-binding.cjs.map +1 -0
  92. package/dist/lib/elur/next-2/node-binding.d.cts +6 -0
  93. package/dist/lib/elur/next-2/node-binding.d.ts +6 -0
  94. package/dist/lib/elur/next-2/node-binding.js +130 -0
  95. package/dist/lib/elur/next-2/node-binding.js.map +1 -0
  96. package/dist/lib/elur/next-2/reactivity.cjs +2 -0
  97. package/dist/lib/elur/next-2/reactivity.cjs.map +1 -0
  98. package/dist/lib/elur/next-2/reactivity.d.cts +220 -0
  99. package/dist/lib/elur/next-2/reactivity.d.ts +220 -0
  100. package/dist/lib/elur/next-2/reactivity.js +614 -0
  101. package/dist/lib/elur/next-2/reactivity.js.map +1 -0
  102. package/dist/lib/elur/next-2/scheduler.cjs +2 -0
  103. package/dist/lib/elur/next-2/scheduler.cjs.map +1 -0
  104. package/dist/lib/elur/next-2/scheduler.d.cts +23 -0
  105. package/dist/lib/elur/next-2/scheduler.d.ts +23 -0
  106. package/dist/lib/elur/next-2/scheduler.js +26 -0
  107. package/dist/lib/elur/next-2/scheduler.js.map +1 -0
  108. package/dist/lib/elur/plugins.cjs +1 -1
  109. package/dist/lib/elur/plugins.cjs.map +1 -1
  110. package/dist/lib/elur/plugins.js +1 -1
  111. package/dist/lib/elur/reactivity.cjs +1 -2
  112. package/dist/lib/elur/reactivity.d.cts +1 -67
  113. package/dist/lib/elur/reactivity.d.ts +1 -67
  114. package/dist/lib/elur/reactivity.js +2 -218
  115. package/dist/lib/elur/router.cjs +1 -1
  116. package/dist/lib/elur/router.cjs.map +1 -1
  117. package/dist/lib/elur/router.js +1 -1
  118. package/dist/lib/elur/server/index.cjs +1 -1
  119. package/dist/lib/elur/server/index.cjs.map +1 -1
  120. package/dist/lib/elur/server/index.js +175 -107
  121. package/dist/lib/elur/server/index.js.map +1 -1
  122. package/dist/lib/elur/store.cjs +1 -1
  123. package/dist/lib/elur/store.cjs.map +1 -1
  124. package/dist/lib/elur/store.js +1 -1
  125. package/dist/lib/elur/template/bindings.cjs +1 -1
  126. package/dist/lib/elur/template/bindings.cjs.map +1 -1
  127. package/dist/lib/elur/template/bindings.js +98 -84
  128. package/dist/lib/elur/template/bindings.js.map +1 -1
  129. package/dist/lib/elur/template/dom-write.d.cts +1 -5
  130. package/dist/lib/elur/template/dom-write.d.ts +1 -5
  131. package/dist/lib/elur/template/error-boundary.cjs +1 -1
  132. package/dist/lib/elur/template/error-boundary.cjs.map +1 -1
  133. package/dist/lib/elur/template/error-boundary.js +56 -50
  134. package/dist/lib/elur/template/error-boundary.js.map +1 -1
  135. package/dist/lib/elur/template/html.cjs +1 -1
  136. package/dist/lib/elur/template/html.cjs.map +1 -1
  137. package/dist/lib/elur/template/html.js +40 -37
  138. package/dist/lib/elur/template/html.js.map +1 -1
  139. package/dist/lib/elur/template/index.cjs +1 -1
  140. package/dist/lib/elur/template/index.d.cts +2 -1
  141. package/dist/lib/elur/template/index.d.ts +2 -1
  142. package/dist/lib/elur/template/index.js +13 -12
  143. package/dist/lib/elur/template/keyed-diff.d.cts +1 -32
  144. package/dist/lib/elur/template/keyed-diff.d.ts +1 -32
  145. package/dist/lib/elur/template/keyed.cjs +1 -1
  146. package/dist/lib/elur/template/keyed.cjs.map +1 -1
  147. package/dist/lib/elur/template/keyed.d.cts +10 -2
  148. package/dist/lib/elur/template/keyed.d.ts +10 -2
  149. package/dist/lib/elur/template/keyed.js +12 -1
  150. package/dist/lib/elur/template/keyed.js.map +1 -1
  151. package/dist/lib/elur/template/mount-helpers.d.cts +1 -29
  152. package/dist/lib/elur/template/mount-helpers.d.ts +1 -29
  153. package/dist/lib/elur/template/node-binding.d.cts +1 -6
  154. package/dist/lib/elur/template/node-binding.d.ts +1 -6
  155. package/dist/lib/elur/template/portal.cjs +1 -1
  156. package/dist/lib/elur/template/portal.cjs.map +1 -1
  157. package/dist/lib/elur/template/portal.js +1 -1
  158. package/dist/lib/elur/template/transitions.cjs +1 -1
  159. package/dist/lib/elur/template/transitions.cjs.map +1 -1
  160. package/dist/lib/elur/template/transitions.js +2 -2
  161. package/dist/lib/elur/template/types.cjs +1 -1
  162. package/dist/lib/elur/template/types.cjs.map +1 -1
  163. package/dist/lib/elur/template/types.d.cts +55 -0
  164. package/dist/lib/elur/template/types.d.ts +55 -0
  165. package/dist/lib/elur/template/types.js +5 -1
  166. package/dist/lib/elur/template/types.js.map +1 -1
  167. package/dist/lib/elur.cjs +1 -1
  168. package/dist/lib/elur.js +2 -2
  169. package/dist/lib/form.cjs +1 -1
  170. package/dist/lib/form.js +1 -1
  171. package/dist/lib/hydrate.cjs +1 -2
  172. package/dist/lib/hydrate.js +2 -410
  173. package/dist/lib/index.cjs +1 -1
  174. package/dist/lib/index.js +2 -2
  175. package/dist/lib/lifecycle.cjs +1 -1
  176. package/dist/lib/lifecycle.js +4 -0
  177. package/dist/lib/plugins.cjs +1 -1
  178. package/dist/lib/plugins.js +1 -1
  179. package/dist/lib/router.cjs +1 -1
  180. package/dist/lib/router.js +1 -1
  181. package/dist/lib/server.cjs +1 -1
  182. package/dist/lib/server.js +175 -107
  183. package/dist/lib/signals.cjs +1 -2
  184. package/dist/lib/signals.js +2 -218
  185. package/dist/lib/store.cjs +1 -1
  186. package/dist/lib/store.js +1 -1
  187. package/dist/lib/template.cjs +1 -1
  188. package/dist/lib/template.js +13 -12
  189. package/package.json +7 -9
  190. package/dist/lib/elur/component.cjs.map +0 -1
  191. package/dist/lib/elur/component.js.map +0 -1
  192. package/dist/lib/elur/hydrate/index.cjs.map +0 -1
  193. package/dist/lib/elur/hydrate/index.js.map +0 -1
  194. package/dist/lib/elur/reactivity.cjs.map +0 -1
  195. package/dist/lib/elur/reactivity.js.map +0 -1
  196. package/dist/lib/elur/template/dom-write.cjs +0 -2
  197. package/dist/lib/elur/template/dom-write.cjs.map +0 -1
  198. package/dist/lib/elur/template/dom-write.js +0 -16
  199. package/dist/lib/elur/template/dom-write.js.map +0 -1
  200. package/dist/lib/elur/template/keyed-diff.cjs +0 -2
  201. package/dist/lib/elur/template/keyed-diff.cjs.map +0 -1
  202. package/dist/lib/elur/template/keyed-diff.js +0 -100
  203. package/dist/lib/elur/template/keyed-diff.js.map +0 -1
  204. package/dist/lib/elur/template/mount-helpers.cjs +0 -2
  205. package/dist/lib/elur/template/mount-helpers.cjs.map +0 -1
  206. package/dist/lib/elur/template/mount-helpers.js +0 -132
  207. package/dist/lib/elur/template/mount-helpers.js.map +0 -1
  208. package/dist/lib/elur/template/node-binding.cjs +0 -2
  209. package/dist/lib/elur/template/node-binding.cjs.map +0 -1
  210. package/dist/lib/elur/template/node-binding.js +0 -95
  211. package/dist/lib/elur/template/node-binding.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","names":[],"sources":["../../src/elur/server/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { _popComponentContext, _pushComponentContext, _setContextScopeResolver } from \"../context.js\";\nimport { isElurComponent, type ElurComponent } from \"../lifecycle.js\";\nimport {\n isKeyedList,\n isElurTemplate,\n ELUR_RENDER_PROTOCOL,\n ELUR_TEMPLATE_DESCRIPTOR,\n type ServerRenderProtocolContext,\n type TemplateBindingContext,\n type TemplateDescriptor,\n} from \"../template/types.js\";\nimport { sanitizeUrl } from \"../template/sanitize.js\";\nimport { normalizeRepeatKey, serializeRepeatKey } from \"../template/keyed.js\";\n\n// Local guards avoid sharing the same minified import binding name with\n// callback parameters in the same module (esbuild bug with shared chunks).\nconst isTemplate = isElurTemplate;\nconst isKeyed = isKeyedList;\n\nexport interface RenderErrorInfo {\n /** Binding index inside the descriptor (or -1 for root/component errors). */\n index: number;\n /** Binding context where the error occurred. */\n context: \"node\" | \"attribute\" | \"event\" | \"root\" | \"component\";\n /** The original error. */\n cause: unknown;\n /** Debug name of the component when the error happened inside one. */\n component?: string;\n}\n\nexport interface ServerRenderOptions {\n markers?: \"none\" | \"hydration\";\n signal?: AbortSignal;\n context?: unknown;\n onError?: (error: unknown, info: RenderErrorInfo) => void;\n}\n\n/** A fragment of the rendered markup, streamed incrementally by `renderToChunks`. */\nexport interface RenderChunk {\n type: \"markup\" | \"boundary-start\" | \"boundary-end\" | \"error\" | \"done\";\n value: string;\n index: number;\n}\n\ninterface RenderState {\n markers: boolean;\n signal?: AbortSignal;\n context?: unknown;\n onError?: (error: unknown, info: RenderErrorInfo) => void;\n}\n\nconst renderContext = new AsyncLocalStorage<Map<unknown, unknown>[]>();\n_setContextScopeResolver(() => renderContext.getStore());\n\nexport interface ServerRenderScope {\n /** Abort signal that cancels all renders created through this scope. */\n readonly signal: AbortSignal;\n /** Renders a value to a full string, sharing this scope's context isolation. */\n render(value: unknown, options?: { markers?: boolean }): Promise<string>;\n /** Streams a value as incremental chunks, sharing this scope's context isolation. */\n renderToChunks(value: unknown, options?: { markers?: boolean }): AsyncIterable<RenderChunk>;\n /** Aborts every render running inside this scope. */\n abort(reason?: unknown): void;\n}\n\n/**\n * Creates an explicit server render scope: a per-render isolation unit that\n * keeps component context, markers and abort state private. Concurrent renders\n * created from the same scope never share `provide`/`inject` state.\n */\nexport function createServerRenderScope(options: Omit<ServerRenderOptions, \"signal\"> = {}): ServerRenderScope {\n const controller = new AbortController();\n const state: RenderState = {\n markers: options.markers === \"hydration\",\n context: options.context,\n onError: options.onError,\n };\n const run = <T>(fn: () => Promise<T> | T): Promise<T> => Promise.resolve(renderContext.run([], fn));\n return {\n signal: controller.signal,\n async render(value, opts): Promise<string> {\n const scoped: RenderState = { ...state, markers: opts?.markers ?? state.markers, signal: controller.signal };\n let out = \"\";\n await run(() => {\n return (async () => {\n for await (const chunk of renderValueChunks(value, scoped)) {\n out += chunk.value;\n }\n })();\n });\n return out;\n },\n renderToChunks(value, opts): AsyncIterable<RenderChunk> {\n const scoped: RenderState = { ...state, markers: opts?.markers ?? state.markers, signal: controller.signal };\n return streamChunks(value, scoped);\n },\n abort(reason?: unknown): void {\n controller.abort(reason);\n },\n };\n}\n\n/**\n * Streams chunks produced inside an AsyncLocalStorage scope, preserving\n * per-render context isolation while yielding incrementally.\n */\nfunction streamChunks(value: unknown, state: RenderState): AsyncIterable<RenderChunk> {\n return {\n async *[Symbol.asyncIterator]() {\n const queue: RenderChunk[] = [];\n const waiters: Array<() => void> = [];\n let producerDone = false;\n let producerError: unknown;\n const wake = () => {\n const waiter = waiters.shift();\n waiter?.();\n };\n\n const task = renderContext.run([], async () => {\n try {\n for await (const chunk of renderValueChunks(value, state)) {\n queue.push(chunk);\n wake();\n }\n } catch (error) {\n producerError = error;\n } finally {\n producerDone = true;\n wake();\n }\n });\n\n while (true) {\n while (queue.length === 0) {\n if (producerDone) break;\n await new Promise<void>((resolve) => waiters.push(resolve));\n }\n if (queue.length === 0) break;\n const chunk = queue.shift()!;\n if (chunk) yield chunk;\n }\n if (producerError) {\n yield { type: \"error\", value: producerError instanceof Error ? producerError.message : String(producerError), index: -1 };\n throw producerError;\n }\n yield { type: \"done\", value: \"\", index: -1 };\n await task;\n },\n };\n}\n\nexport async function renderToString(value: unknown, options: ServerRenderOptions = {}): Promise<string> {\n const state: RenderState = {\n markers: options.markers === \"hydration\",\n signal: options.signal,\n context: options.context,\n onError: options.onError,\n };\n let out = \"\";\n await Promise.resolve(\n renderContext.run([], async () => {\n for await (const chunk of renderValueChunks(value, state)) {\n out += chunk.value;\n }\n }),\n );\n return out;\n}\n\nexport function renderToChunks(\n value: unknown,\n options: ServerRenderOptions = {},\n): AsyncIterable<RenderChunk> {\n const state: RenderState = {\n markers: options.markers === \"hydration\",\n signal: options.signal,\n context: options.context,\n onError: options.onError,\n };\n return streamChunks(value, state);\n}\n\nasync function* renderValueChunks(value: unknown, state: RenderState): AsyncGenerator<RenderChunk> {\n checkAbort(state);\n if (value instanceof Promise) {\n yield* renderValueChunks(await value, state);\n return;\n }\n if (value === null || value === undefined || value === false || value === true) return;\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"bigint\") {\n yield { type: \"markup\", value: escapeText(String(value)), index: -1 };\n return;\n }\n if (Array.isArray(value)) {\n const resolved = await Promise.all(value.map((item) => resolveBindingValue(item)));\n checkAbort(state);\n for (const item of resolved) {\n if (state.markers) yield { type: \"markup\", value: \"<!--elur-ai-->\", index: -1 };\n yield* renderValueChunks(item, state);\n if (state.markers) yield { type: \"markup\", value: \"<!--elur-aiend-->\", index: -1 };\n }\n return;\n }\n if ((typeof value === \"object\" || typeof value === \"function\") && value !== null) {\n const protocol = (value as Record<PropertyKey, unknown>)[ELUR_RENDER_PROTOCOL] as\n | { renderServer?: (context: ServerRenderProtocolContext) => string | Promise<string> }\n | undefined;\n if (protocol?.renderServer) {\n const output = await protocol.renderServer({\n markers: state.markers,\n signal: state.signal,\n context: state.context,\n render: (nested, options) => renderValue(nested, {\n ...state,\n markers: options?.markers ?? state.markers,\n }),\n });\n checkAbort(state);\n yield { type: \"markup\", value: output, index: -1 };\n return;\n }\n }\n if (isElurComponent(value)) {\n yield* renderComponentChunks(value, state);\n return;\n }\n if (isKeyed(value)) {\n if (!state.markers) {\n const rendered = await Promise.all(\n value.items.map((item, index) => renderValue(value.renderFn(item, index), state)),\n );\n yield { type: \"markup\", value: rendered.join(\"\"), index: -1 };\n return;\n }\n const seen = new Set<string>();\n for (let index = 0; index < value.items.length; index++) {\n const item = value.items[index];\n const key = normalizeRepeatKey(value.keyFn(item, index), index);\n const serialized = serializeRepeatKey(key);\n if (seen.has(serialized)) {\n console.warn(\n `[elur] repeat(): duplicate key \"${key}\" during server render. ` +\n \"Keys must be unique; entries after the first will leak during hydration.\",\n );\n }\n seen.add(serialized);\n yield { type: \"markup\", value: `<!--elur-ki:${serialized}-->`, index: -1 };\n yield* renderValueChunks(value.renderFn(item, index), state);\n yield { type: \"markup\", value: \"<!--elur-ke-->\", index: -1 };\n }\n return;\n }\n if (isTemplate(value)) {\n const descriptor = value[ELUR_TEMPLATE_DESCRIPTOR];\n if (!descriptor) throw new TypeError(\"[elur] Template does not support server rendering\");\n yield* renderDescriptorChunks(descriptor, state);\n return;\n }\n yield { type: \"markup\", value: escapeText(String(value)), index: -1 };\n}\n\nasync function* renderComponentChunks(component: ElurComponent, state: RenderState): AsyncGenerator<RenderChunk> {\n _pushComponentContext();\n const info: RenderErrorInfo = {\n index: -1,\n context: \"component\",\n cause: undefined,\n component: component._debugName,\n };\n try {\n try {\n component.onInit?.();\n component.onServerRender?.();\n } catch (error) {\n if (component.onError) component.onError(error);\n else throw error;\n }\n try {\n yield* renderValueChunks(component.render(), state);\n } catch (error) {\n if (component.onError) {\n component.onError(error);\n return;\n }\n throw error;\n }\n } catch (error) {\n state.onError?.(error, { ...info, cause: error });\n throw error;\n } finally {\n _popComponentContext();\n }\n}\n\nasync function* renderDescriptorChunks(descriptor: TemplateDescriptor, state: RenderState): AsyncGenerator<RenderChunk> {\n const skipLeading = new Uint8Array(descriptor.strings.length);\n\n for (let index = 0; index < descriptor.strings.length; index++) {\n if (state.signal?.aborted) throw state.signal.reason ?? new DOMException(\"Render aborted\", \"AbortError\");\n let staticPart = descriptor.strings[index];\n if (skipLeading[index] === 1 && (staticPart[0] === '\"' || staticPart[0] === \"'\")) {\n staticPart = staticPart.slice(1);\n }\n if (index >= descriptor.contexts.length) {\n yield { type: \"markup\", value: staticPart, index };\n continue;\n }\n\n const context = descriptor.contexts[index];\n const value = descriptor.values[index];\n if (context.type === \"node\") {\n yield { type: \"markup\", value: staticPart, index };\n if (state.markers) yield { type: \"boundary-start\", value: `<!--elur-${index}-->`, index };\n try {\n const resolved = await resolveBindingValue(value);\n checkAbort(state);\n yield* renderValueChunks(resolved, state);\n } catch (error) {\n state.onError?.(error, { index, context: \"node\", cause: error });\n throw error;\n }\n if (state.markers) yield { type: \"boundary-end\", value: `<!--elur-end-${index}-->`, index };\n continue;\n }\n\n const cut = bindingCut(context);\n const prefix = staticPart.slice(0, -cut);\n if (context.hadOpenQuote) skipLeading[index + 1] = 1;\n if (context.type === \"event\") {\n if (state.markers) {\n const separator = /\\s$/.test(prefix) ? \"\" : \" \";\n yield { type: \"markup\", value: `${prefix}${separator}data-elur-e-${index}=\"${escapeAttribute(context.eventName)}\"`, index };\n } else {\n yield { type: \"markup\", value: prefix.replace(/\\s+$/, \"\"), index };\n }\n continue;\n }\n\n yield { type: \"markup\", value: prefix, index };\n const resolved = await resolveBindingValue(value);\n checkAbort(state);\n if (context.attrName !== \"ref\" && resolved !== null && resolved !== undefined && resolved !== false) {\n const serialized = context.url ? sanitizeUrl(String(resolved)) : String(resolved);\n const separator = /\\s$/.test(prefix) ? \"\" : \" \";\n yield { type: \"markup\", value: `${separator}${context.attrName}=\"${escapeAttribute(serialized)}\"`, index };\n }\n if (state.markers) yield { type: \"markup\", value: ` data-elur-a-${index}=\"${escapeAttribute(context.attrName)}\"`, index };\n }\n}\n\nasync function renderValue(value: unknown, state: RenderState): Promise<string> {\n let out = \"\";\n for await (const chunk of renderValueChunks(value, state)) {\n out += chunk.value;\n }\n return out;\n}\n\nfunction checkAbort(state: RenderState): void {\n if (state.signal?.aborted) throw state.signal.reason ?? new DOMException(\"Render aborted\", \"AbortError\");\n}\n\nasync function resolveBindingValue(value: unknown): Promise<unknown> {\n const resolved = typeof value === \"function\" ? (value as () => unknown)() : value;\n return resolved instanceof Promise ? await resolved : resolved;\n}\n\nfunction bindingCut(context: Exclude<TemplateBindingContext, { type: \"node\" }>): number {\n if (context.type === \"event\") {\n const full = context.modifiers.length\n ? `${context.eventName}.${context.modifiers.join(\".\")}`\n : context.eventName;\n return `@${full}=`.length + (context.hadOpenQuote ? 1 : 0);\n }\n return `${context.attrName}=`.length + (context.hadOpenQuote ? 1 : 0);\n}\n\nfunction escapeText(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n\nfunction escapeAttribute(value: string): string {\n return escapeText(value).replace(/\"/g, \"&quot;\");\n}"],"mappings":"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"}
1
+ {"version":3,"file":"server.js","names":[],"sources":["../../src/elur/server/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { _popComponentContext, _pushComponentContext, _setContextScopeResolver } from \"../context.js\";\nimport { isElurComponent, type ElurComponent } from \"../lifecycle.js\";\nimport {\n isKeyedList,\n isElurTemplate,\n ELUR_RENDER_PROTOCOL,\n ELUR_TEMPLATE_DESCRIPTOR,\n type ServerRenderProtocolContext,\n type SsrEmit,\n type TemplateBindingContext,\n type TemplateDescriptor,\n} from \"../template/types.js\";\nimport { sanitizeUrl } from \"../template/sanitize.js\";\nimport { normalizeRepeatKey, serializeRepeatKey } from \"../template/keyed.js\";\nimport { Signal, untrack } from \"../reactivity.js\";\nimport { isDerivedBinding } from \"../template/types.js\";\n\n// Local guards avoid sharing the same minified import binding name with\n// callback parameters in the same module (esbuild bug with shared chunks).\nconst isTemplate = isElurTemplate;\nconst isKeyed = isKeyedList;\n\nexport interface RenderErrorInfo {\n /** Binding index inside the descriptor (or -1 for root/component errors). */\n index: number;\n /** Binding context where the error occurred. */\n context: \"node\" | \"attribute\" | \"event\" | \"root\" | \"component\";\n /** The original error. */\n cause: unknown;\n /** Debug name of the component when the error happened inside one. */\n component?: string;\n}\n\nexport interface ServerRenderOptions {\n markers?: \"none\" | \"hydration\";\n signal?: AbortSignal;\n context?: unknown;\n onError?: (error: unknown, info: RenderErrorInfo) => void;\n}\n\n/** A fragment of the rendered markup, streamed incrementally by `renderToChunks`. */\nexport interface RenderChunk {\n type: \"markup\" | \"boundary-start\" | \"boundary-end\" | \"error\" | \"done\";\n value: string;\n index: number;\n}\n\ninterface RenderState {\n markers: boolean;\n signal?: AbortSignal;\n context?: unknown;\n onError?: (error: unknown, info: RenderErrorInfo) => void;\n}\n\nconst renderContext = new AsyncLocalStorage<Map<unknown, unknown>[]>();\n_setContextScopeResolver(() => renderContext.getStore());\n\nexport interface ServerRenderScope {\n /** Abort signal that cancels all renders created through this scope. */\n readonly signal: AbortSignal;\n /** Renders a value to a full string, sharing this scope's context isolation. */\n render(value: unknown, options?: { markers?: boolean }): Promise<string>;\n /** Streams a value as incremental chunks, sharing this scope's context isolation. */\n renderToChunks(value: unknown, options?: { markers?: boolean }): AsyncIterable<RenderChunk>;\n /** Aborts every render running inside this scope. */\n abort(reason?: unknown): void;\n}\n\n/**\n * Creates an explicit server render scope: a per-render isolation unit that\n * keeps component context, markers and abort state private. Concurrent renders\n * created from the same scope never share `provide`/`inject` state.\n */\nexport function createServerRenderScope(options: Omit<ServerRenderOptions, \"signal\"> = {}): ServerRenderScope {\n const controller = new AbortController();\n const state: RenderState = {\n markers: options.markers === \"hydration\",\n context: options.context,\n onError: options.onError,\n };\n const run = <T>(fn: () => Promise<T> | T): Promise<T> => Promise.resolve(renderContext.run([], fn));\n return {\n signal: controller.signal,\n async render(value, opts): Promise<string> {\n const scoped: RenderState = { ...state, markers: opts?.markers ?? state.markers, signal: controller.signal };\n let out = \"\";\n await run(() => {\n return (async () => {\n for await (const chunk of renderValueChunks(value, scoped)) {\n out += chunk.value;\n }\n })();\n });\n return out;\n },\n renderToChunks(value, opts): AsyncIterable<RenderChunk> {\n const scoped: RenderState = { ...state, markers: opts?.markers ?? state.markers, signal: controller.signal };\n return streamChunks(value, scoped);\n },\n abort(reason?: unknown): void {\n controller.abort(reason);\n },\n };\n}\n\n/**\n * Streams chunks produced inside an AsyncLocalStorage scope, preserving\n * per-render context isolation while yielding incrementally.\n */\nfunction streamChunks(value: unknown, state: RenderState): AsyncIterable<RenderChunk> {\n return {\n async *[Symbol.asyncIterator]() {\n const queue: RenderChunk[] = [];\n const waiters: Array<() => void> = [];\n let producerDone = false;\n let producerError: unknown;\n const wake = () => {\n const waiter = waiters.shift();\n waiter?.();\n };\n\n const task = renderContext.run([], async () => {\n try {\n for await (const chunk of renderValueChunks(value, state)) {\n queue.push(chunk);\n wake();\n }\n } catch (error) {\n producerError = error;\n } finally {\n producerDone = true;\n wake();\n }\n });\n\n while (true) {\n while (queue.length === 0) {\n if (producerDone) break;\n await new Promise<void>((resolve) => waiters.push(resolve));\n }\n if (queue.length === 0) break;\n const chunk = queue.shift()!;\n if (chunk) yield chunk;\n }\n if (producerError) {\n yield { type: \"error\", value: producerError instanceof Error ? producerError.message : String(producerError), index: -1 };\n throw producerError;\n }\n yield { type: \"done\", value: \"\", index: -1 };\n await task;\n },\n };\n}\n\nexport async function renderToString(value: unknown, options: ServerRenderOptions = {}): Promise<string> {\n const state: RenderState = {\n markers: options.markers === \"hydration\",\n signal: options.signal,\n context: options.context,\n onError: options.onError,\n };\n let out = \"\";\n await Promise.resolve(\n renderContext.run([], async () => {\n for await (const chunk of renderValueChunks(value, state)) {\n out += chunk.value;\n }\n }),\n );\n return out;\n}\n\nexport function renderToChunks(\n value: unknown,\n options: ServerRenderOptions = {},\n): AsyncIterable<RenderChunk> {\n const state: RenderState = {\n markers: options.markers === \"hydration\",\n signal: options.signal,\n context: options.context,\n onError: options.onError,\n };\n return streamChunks(value, state);\n}\n\nasync function* renderValueChunks(value: unknown, state: RenderState): AsyncGenerator<RenderChunk> {\n checkAbort(state);\n if (value instanceof Promise) {\n yield* renderValueChunks(await value, state);\n return;\n }\n if (value === null || value === undefined || value === false || value === true) return;\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"bigint\") {\n yield { type: \"markup\", value: escapeText(String(value)), index: -1 };\n return;\n }\n if (Array.isArray(value)) {\n const resolved = await Promise.all(value.map((item) => resolveBindingValue(item)));\n checkAbort(state);\n for (const item of resolved) {\n if (state.markers) yield { type: \"markup\", value: \"<!--elur-ai-->\", index: -1 };\n yield* renderValueChunks(item, state);\n if (state.markers) yield { type: \"markup\", value: \"<!--elur-aiend-->\", index: -1 };\n }\n return;\n }\n if ((typeof value === \"object\" || typeof value === \"function\") && value !== null) {\n const protocol = (value as Record<PropertyKey, unknown>)[ELUR_RENDER_PROTOCOL] as\n | { renderServer?: (context: ServerRenderProtocolContext) => string | Promise<string> }\n | undefined;\n if (protocol?.renderServer) {\n const output = await protocol.renderServer({\n markers: state.markers,\n signal: state.signal,\n context: state.context,\n render: (nested, options) => renderValue(nested, {\n ...state,\n markers: options?.markers ?? state.markers,\n }),\n });\n checkAbort(state);\n yield { type: \"markup\", value: output, index: -1 };\n return;\n }\n }\n if (isElurComponent(value)) {\n yield* renderComponentChunks(value, state);\n return;\n }\n if (isKeyed(value)) {\n if (!state.markers) {\n const rendered = await Promise.all(\n value.items.map((item, index) => renderValue(value.renderFn(item, index), state)),\n );\n yield { type: \"markup\", value: rendered.join(\"\"), index: -1 };\n return;\n }\n const seen = new Set<string>();\n for (let index = 0; index < value.items.length; index++) {\n const item = value.items[index];\n const key = normalizeRepeatKey(value.keyFn(item, index), index);\n const serialized = serializeRepeatKey(key);\n if (seen.has(serialized)) {\n console.warn(\n `[elur] repeat(): duplicate key \"${key}\" during server render. ` +\n \"Keys must be unique; entries after the first will leak during hydration.\",\n );\n }\n seen.add(serialized);\n yield { type: \"markup\", value: `<!--elur-ki:${serialized}-->`, index: -1 };\n yield* renderValueChunks(value.renderFn(item, index), state);\n yield { type: \"markup\", value: \"<!--elur-ke-->\", index: -1 };\n }\n return;\n }\n if (isTemplate(value)) {\n const descriptor = value[ELUR_TEMPLATE_DESCRIPTOR];\n if (!descriptor) throw new TypeError(\"[elur] Template does not support server rendering\");\n yield* renderDescriptorChunks(descriptor, state);\n return;\n }\n yield { type: \"markup\", value: escapeText(String(value)), index: -1 };\n}\n\nasync function* renderComponentChunks(component: ElurComponent, state: RenderState): AsyncGenerator<RenderChunk> {\n _pushComponentContext();\n const info: RenderErrorInfo = {\n index: -1,\n context: \"component\",\n cause: undefined,\n component: component._debugName,\n };\n try {\n try {\n component.onInit?.();\n component.onServerRender?.();\n } catch (error) {\n if (component.onError) component.onError(error);\n else throw error;\n }\n try {\n yield* renderValueChunks(component.render(), state);\n } catch (error) {\n if (component.onError) {\n component.onError(error);\n return;\n }\n throw error;\n }\n } catch (error) {\n state.onError?.(error, { ...info, cause: error });\n throw error;\n } finally {\n _popComponentContext();\n }\n}\n\n// C.12 fase 2: emit ops para el renderer SSR especializado (descriptor.ssr).\n// Replican exactamente la semántica del intérprete genérico — los strings\n// llegan pre-cortados desde el codegen (bindingCut/skipLeading en build).\nfunction makeSsrEmit(state: RenderState, compiledHydrate: boolean): SsrEmit {\n return {\n m: (value) => ({ type: \"markup\", value, index: -1 }),\n node: async function* (index, value) {\n if (state.markers) yield { type: \"boundary-start\", value: `<!--elur-${index}-->`, index };\n try {\n const resolved = await resolveBindingValue(value);\n checkAbort(state);\n yield* renderValueChunks(resolved, state);\n } catch (error) {\n state.onError?.(error, { index, context: \"node\", cause: error });\n throw error;\n }\n if (state.markers) yield { type: \"boundary-end\", value: `<!--elur-end-${index}-->`, index };\n },\n attr: async (prefix, index, attrName, url, _executable, value) => {\n const chunks: RenderChunk[] = [{ type: \"markup\", value: prefix, index }];\n const resolved = await resolveBindingValue(value);\n checkAbort(state);\n if (attrName !== \"ref\" && resolved !== null && resolved !== undefined && resolved !== false) {\n const serialized = url ? sanitizeUrl(String(resolved)) : String(resolved);\n const separator = /\\s$/.test(prefix) ? \"\" : \" \";\n chunks.push({ type: \"markup\", value: `${separator}${attrName}=\"${escapeAttribute(serialized)}\"`, index });\n }\n if (state.markers && !compiledHydrate) {\n chunks.push({ type: \"markup\", value: ` data-elur-a-${index}=\"${escapeAttribute(attrName)}\"`, index });\n }\n return chunks;\n },\n event: (index, prefix, eventName) => {\n if (state.markers && !compiledHydrate) {\n const separator = /\\s$/.test(prefix) ? \"\" : \" \";\n return { type: \"markup\", value: `${prefix}${separator}data-elur-e-${index}=\"${escapeAttribute(eventName)}\"`, index };\n }\n return { type: \"markup\", value: prefix.replace(/\\s+$/, \"\"), index };\n },\n };\n}\n\nasync function* renderDescriptorChunks(descriptor: TemplateDescriptor, state: RenderState): AsyncGenerator<RenderChunk> {\n // C.13: templates con hidratación compilada no necesitan `data-elur-*`\n // en el HTML — el cliente localiza los elementos por access paths.\n // Los `<!--elur-N-->` siguen emitiéndose (boundaries dinámicas).\n const compiledHydrate = descriptor.hydrate !== undefined;\n // C.12 fase 2: el artefacto compilado trae su propio renderer SSR —\n // mismo HTML, secuencia especializada en vez del loop intérprete.\n if (descriptor.ssr) {\n yield* descriptor.ssr(makeSsrEmit(state, compiledHydrate), descriptor.values);\n return;\n }\n const skipLeading = new Uint8Array(descriptor.strings.length);\n\n for (let index = 0; index < descriptor.strings.length; index++) {\n if (state.signal?.aborted) throw state.signal.reason ?? new DOMException(\"Render aborted\", \"AbortError\");\n let staticPart = descriptor.strings[index];\n if (skipLeading[index] === 1 && (staticPart[0] === '\"' || staticPart[0] === \"'\")) {\n staticPart = staticPart.slice(1);\n }\n if (index >= descriptor.contexts.length) {\n yield { type: \"markup\", value: staticPart, index };\n continue;\n }\n\n const context = descriptor.contexts[index];\n const value = descriptor.values[index];\n if (context.type === \"node\") {\n yield { type: \"markup\", value: staticPart, index };\n if (state.markers) yield { type: \"boundary-start\", value: `<!--elur-${index}-->`, index };\n try {\n const resolved = await resolveBindingValue(value);\n checkAbort(state);\n yield* renderValueChunks(resolved, state);\n } catch (error) {\n state.onError?.(error, { index, context: \"node\", cause: error });\n throw error;\n }\n if (state.markers) yield { type: \"boundary-end\", value: `<!--elur-end-${index}-->`, index };\n continue;\n }\n\n const cut = bindingCut(context);\n const prefix = staticPart.slice(0, -cut);\n if (context.hadOpenQuote) skipLeading[index + 1] = 1;\n if (context.type === \"event\") {\n if (state.markers && !compiledHydrate) {\n const separator = /\\s$/.test(prefix) ? \"\" : \" \";\n yield { type: \"markup\", value: `${prefix}${separator}data-elur-e-${index}=\"${escapeAttribute(context.eventName)}\"`, index };\n } else {\n yield { type: \"markup\", value: prefix.replace(/\\s+$/, \"\"), index };\n }\n continue;\n }\n\n yield { type: \"markup\", value: prefix, index };\n const resolved = await resolveBindingValue(value);\n checkAbort(state);\n if (context.attrName !== \"ref\" && resolved !== null && resolved !== undefined && resolved !== false) {\n const serialized = context.url ? sanitizeUrl(String(resolved)) : String(resolved);\n const separator = /\\s$/.test(prefix) ? \"\" : \" \";\n yield { type: \"markup\", value: `${separator}${context.attrName}=\"${escapeAttribute(serialized)}\"`, index };\n }\n if (state.markers && !compiledHydrate) yield { type: \"markup\", value: ` data-elur-a-${index}=\"${escapeAttribute(context.attrName)}\"`, index };\n }\n}\n\nasync function renderValue(value: unknown, state: RenderState): Promise<string> {\n let out = \"\";\n for await (const chunk of renderValueChunks(value, state)) {\n out += chunk.value;\n }\n return out;\n}\n\nfunction checkAbort(state: RenderState): void {\n if (state.signal?.aborted) throw state.signal.reason ?? new DOMException(\"Render aborted\", \"AbortError\");\n}\n\nasync function resolveBindingValue(value: unknown): Promise<unknown> {\n // C.6 T1: la señal viaja como valor — SSR la resuelve a su lectura actual.\n if (value instanceof Signal) return value.peek();\n // C.7 T2: el pack derivado se resuelve evaluando el getter untracked —\n // SSR no crea suscripciones y no debe contaminar un consumidor activo.\n if (isDerivedBinding(value)) return untrack(value.get);\n const resolved = typeof value === \"function\" ? (value as () => unknown)() : value;\n return resolved instanceof Promise ? await resolved : resolved;\n}\n\nfunction bindingCut(context: Exclude<TemplateBindingContext, { type: \"node\" }>): number {\n if (context.type === \"event\") {\n const full = context.modifiers.length\n ? `${context.eventName}.${context.modifiers.join(\".\")}`\n : context.eventName;\n return `@${full}=`.length + (context.hadOpenQuote ? 1 : 0);\n }\n return `${context.attrName}=`.length + (context.hadOpenQuote ? 1 : 0);\n}\n\nfunction escapeText(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n\nfunction escapeAttribute(value: string): string {\n return escapeText(value).replace(/\"/g, \"&quot;\");\n}"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./signals.cjs`);var t=class extends e.Signal{label;constructor(e,t=`ReadonlySignal`){super(e.peek()),this.label=t,Object.defineProperty(this,`value`,{get:()=>e.value,set:()=>{throw Error(`[elur] "${this.label}" is read-only.`)},configurable:!1}),this.update=()=>{throw Error(`[elur] "${this.label}" is read-only.`)},this.dispose=()=>{throw Error(`[elur] Cannot dispose "${this.label}" directly.`)}}},n=new Set([`$id`,`$state`,`$stateSignal`,`$snapshot`,`$reset`,`$patch`,`$watch`,`$dispose`]);function r(e){if(e===`__proto__`||e===`constructor`||e===`prototype`)throw Error(`[elur] Store key "${e}" is not allowed for security reasons.`);if(n.has(e))throw Error(`[elur] Store key "${e}" is reserved.`)}function i(e,t){return n.has(e)?(console.warn(`[elur] Store ${t} "${e}" is reserved and will be ignored.`),!1):!0}function a(e,n){return new t(e,n)}function o(t,o){let{name:s=`store`,actions:c,getters:l,plugins:u=[],serialize:d}=o??{},f=Object.keys(t),p={};for(let n of f)r(n),p[n]=e.signal(t[n]);let m=p,h=e.computed(()=>{let e={};for(let t of f)e[t]=p[t].value;return e}),g=a(h,`store "${s}".$stateSignal`),_;try{_=d?d(t):structuredClone(t)}catch(e){throw Error(`[elur] Store "${s}" initialState contains non-serializable data (functions, DOM nodes, Symbols, or WeakRefs). Provide a custom \`serialize\` option or remove these before creating the store. Original error: ${e}`)}let v=[];function y(){let t=x(),n=_;for(let e of v){let r=e(n,t);r!==void 0&&(n={...n,...r})}e.batch(()=>{for(let e of f)p[e].value=n[e]})}function b(t){let n=t,r=x();for(let e of v){let t=e(n,r);t!==void 0&&(n=t)}e.batch(()=>{for(let e of Object.keys(n))Object.prototype.hasOwnProperty.call(p,e)&&(p[e].value=n[e])})}function x(){let e={};for(let t of f)e[t]=p[t].peek();return e}function S(t,n){return e.watch(h,t,n)}let C=Object.assign(Object.create(null),m,{$reset:y,$patch:b,$watch:S,$snapshot:x});Object.defineProperty(C,`$id`,{value:s,writable:!1,enumerable:!1,configurable:!1}),Object.defineProperty(C,`$state`,{get(){return h.value},enumerable:!0,configurable:!1}),Object.defineProperty(C,`$stateSignal`,{value:g,writable:!1,enumerable:!1,configurable:!1}),Object.defineProperty(C,`_guardFns`,{value:v,writable:!1,enumerable:!1,configurable:!1});let w=new Set([...f,...Array.from(n)]);if(c){let e=c(m);for(let t of Object.keys(e))if(i(t,`action`)){if(w.has(t)){console.warn(`[elur] Store "${s}": action "${t}" collides with an existing signal or getter and will be ignored.`);continue}w.add(t),C[t]=e[t]}}if(l){let t=l(m);for(let n of Object.keys(t)){if(!i(n,`getter`))continue;if(w.has(n)){console.warn(`[elur] Store "${s}": getter "${n}" collides with an existing signal or action and will be ignored.`);continue}let r=t[n];if(!(r instanceof e.Signal))throw TypeError(`[elur] Store "${s}": getter "${n}" must return a Signal (wrap it with computed()). Got: ${typeof r}`);w.add(n),C[n]=a(r,`getter "${n}" in store "${s}"`)}}let T=[()=>h.dispose()];for(let e of u)try{let t=e(C);typeof t==`function`&&T.push(t)}catch(e){console.error(`[elur] Plugin initialization failed for store "${s}":`,e)}return C.$dispose=()=>{for(let e of T)e()},C}exports.ReadonlySignal=t,exports.createStore=o;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./elur/next-2/reactivity.cjs`);var t=class extends e.Signal{label;constructor(e,t=`ReadonlySignal`){super(e.peek()),this.label=t,Object.defineProperty(this,`value`,{get:()=>e.value,set:()=>{throw Error(`[elur] "${this.label}" is read-only.`)},configurable:!1}),this.update=()=>{throw Error(`[elur] "${this.label}" is read-only.`)},this.dispose=()=>{throw Error(`[elur] Cannot dispose "${this.label}" directly.`)}}},n=new Set([`$id`,`$state`,`$stateSignal`,`$snapshot`,`$reset`,`$patch`,`$watch`,`$dispose`]);function r(e){if(e===`__proto__`||e===`constructor`||e===`prototype`)throw Error(`[elur] Store key "${e}" is not allowed for security reasons.`);if(n.has(e))throw Error(`[elur] Store key "${e}" is reserved.`)}function i(e,t){return n.has(e)?(console.warn(`[elur] Store ${t} "${e}" is reserved and will be ignored.`),!1):!0}function a(e,n){return new t(e,n)}function o(t,o){let{name:s=`store`,actions:c,getters:l,plugins:u=[],serialize:d}=o??{},f=Object.keys(t),p={};for(let n of f)r(n),p[n]=e.signal(t[n]);let m=p,h=e.computed(()=>{let e={};for(let t of f)e[t]=p[t].value;return e}),g=a(h,`store "${s}".$stateSignal`),_;try{_=d?d(t):structuredClone(t)}catch(e){throw Error(`[elur] Store "${s}" initialState contains non-serializable data (functions, DOM nodes, Symbols, or WeakRefs). Provide a custom \`serialize\` option or remove these before creating the store. Original error: ${e}`)}let v=[];function y(){let t=x(),n=_;for(let e of v){let r=e(n,t);r!==void 0&&(n={...n,...r})}e.batch(()=>{for(let e of f)p[e].value=n[e]})}function b(t){let n=t,r=x();for(let e of v){let t=e(n,r);t!==void 0&&(n=t)}e.batch(()=>{for(let e of Object.keys(n))Object.prototype.hasOwnProperty.call(p,e)&&(p[e].value=n[e])})}function x(){let e={};for(let t of f)e[t]=p[t].peek();return e}function S(t,n){return e.watch(h,t,n)}let C=Object.assign(Object.create(null),m,{$reset:y,$patch:b,$watch:S,$snapshot:x});Object.defineProperty(C,`$id`,{value:s,writable:!1,enumerable:!1,configurable:!1}),Object.defineProperty(C,`$state`,{get(){return h.value},enumerable:!0,configurable:!1}),Object.defineProperty(C,`$stateSignal`,{value:g,writable:!1,enumerable:!1,configurable:!1}),Object.defineProperty(C,`_guardFns`,{value:v,writable:!1,enumerable:!1,configurable:!1});let w=new Set([...f,...Array.from(n)]);if(c){let e=c(m);for(let t of Object.keys(e))if(i(t,`action`)){if(w.has(t)){console.warn(`[elur] Store "${s}": action "${t}" collides with an existing signal or getter and will be ignored.`);continue}w.add(t),C[t]=e[t]}}if(l){let t=l(m);for(let n of Object.keys(t)){if(!i(n,`getter`))continue;if(w.has(n)){console.warn(`[elur] Store "${s}": getter "${n}" collides with an existing signal or action and will be ignored.`);continue}let r=t[n];if(!(r instanceof e.Signal))throw TypeError(`[elur] Store "${s}": getter "${n}" must return a Signal (wrap it with computed()). Got: ${typeof r}`);w.add(n),C[n]=a(r,`getter "${n}" in store "${s}"`)}}let T=[()=>h.dispose()];for(let e of u)try{let t=e(C);typeof t==`function`&&T.push(t)}catch(e){console.error(`[elur] Plugin initialization failed for store "${s}":`,e)}return C.$dispose=()=>{for(let e of T)e()},C}exports.ReadonlySignal=t,exports.createStore=o;
2
2
  //# sourceMappingURL=store.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"store.cjs","names":[],"sources":["../../src/elur/store.ts"],"sourcesContent":["import {\n Signal,\n signal,\n computed,\n batch,\n watch,\n type WatchOptions,\n} from \"./reactivity.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Maps a plain state object T into a record of Signals — one per key.\n * This is what the actions/getters factories receive as their argument.\n *\n * Given: { count: number, name: string }\n * Becomes: { count: Signal<number>, name: Signal<string> }\n */\nexport type StoreSignals<T extends object> = {\n readonly [K in keyof T]: Signal<T[K]>;\n};\n\n/**\n * A read-only Signal — extends Signal so it satisfies `instanceof Signal`\n * checks (used by `watch()`), but throws on any mutation attempt.\n */\nexport class ReadonlySignal<T> extends Signal<T> {\n private readonly label: string;\n constructor(source: Signal<T>, label: string = \"ReadonlySignal\") {\n super(source.peek());\n this.label = label;\n\n Object.defineProperty(this, \"value\", {\n get: () => source.value,\n set: () => { throw new Error(`[elur] \"${this.label}\" is read-only.`); },\n configurable: false,\n });\n\n this.update = () => { throw new Error(`[elur] \"${this.label}\" is read-only.`); };\n this.dispose = () => { throw new Error(`[elur] Cannot dispose \"${this.label}\" directly.`); };\n }\n}\n\n/**\n * Maps a getters factory result (a record of Signals) into a record of\n * read-only Signals exposed on the store.\n *\n * The Omit pattern enforces `readonly value` at the type level — TypeScript\n * blocks `getter.value = x` at compile time. The runtime ReadonlySignal class\n * also throws on mutation as defense in depth.\n */\nexport type StoreGetters<G extends Record<string, Signal<unknown>>> = {\n readonly [K in keyof G]: Omit<ReadonlySignal<G[K] extends Signal<infer V> ? V : never>, \"value\"> & {\n readonly value: G[K] extends Signal<infer V> ? V : never;\n };\n};\n\n/**\n * The full store type — combines reactive state signals, action methods,\n * computed getters (as ReadonlySignals), and the framework-level $-prefixed API.\n */\nexport type Store<\n T extends object,\n A extends object = Record<never, never>,\n G extends Record<string, Signal<unknown>> = Record<never, never>,\n> = StoreSignals<T> & A & StoreGetters<G> & {\n readonly $id: string;\n /** Reactive snapshot — reading inside effect/computed creates a subscription to the whole state. */\n readonly $state: T;\n /**\n * Passive snapshot — returns the current state values WITHOUT creating\n * a reactive subscription. Use this in plugins, loggers, persistence,\n * or anywhere you need a one-shot read.\n */\n $snapshot(): T;\n /**\n * The computed Signal that backs $state. Plugins receive this to\n * compose new reactive nodes on top.\n */\n readonly $stateSignal: ReadonlySignal<T>;\n /** Reset to initial values (batched). */\n $reset(): void;\n /** Partial update (batched). */\n $patch(partial: Partial<T>): void;\n /** Watches state changes. Equivalent to `watch(store.$stateSignal, cb, opts)`. */\n $watch(cb: (next: T, prev: T | undefined) => void, options?: WatchOptions): () => void;\n /** Disposes the store and runs all plugin cleanups. */\n $dispose(): void;\n};\n\n// ---------------------------------------------------------------------------\n// Factory types\n// ---------------------------------------------------------------------------\n\nexport type ActionsFactory<\n T extends object,\n A extends object,\n> = (signals: StoreSignals<T>) => A;\n\nexport type GettersFactory<\n T extends object,\n G extends Record<string, Signal<unknown>>,\n> = (signals: StoreSignals<T>) => G;\n\n// ---------------------------------------------------------------------------\n// Plugin type\n// ---------------------------------------------------------------------------\n\n/**\n * Function type for mutation guards.\n * Intercepts $patch and $reset to validate or transform state before it is applied.\n */\nexport type GuardFn<T extends object> = (\n next: Partial<T>,\n current: T,\n) => Partial<T> | void;\n\n/**\n * A ElurPlugin is a function that receives the assembled store and\n * optionally returns a cleanup function called on $dispose().\n *\n * There are NO lifecycle hooks. Plugins extend the signal graph directly\n * using the framework primitives:\n *\n * watch(store.$stateSignal, ...) — react to any state change\n * computed(() => store.someSignal.value) — derive new nodes\n * store.$snapshot() — passive read for logging/persistence\n */\nexport type ElurPlugin<\n T extends object,\n A extends object = Record<never, never>,\n G extends Record<string, Signal<unknown>> = Record<never, never>,\n> = (store: Store<T, A, G>) => (() => void) | void;\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\n/**\n * Options object for createStore. NoInfer<T> on factory parameters ensures\n * T is inferred ONLY from initialState, never from the factories — this is\n * what restores the type inference that broke in v2.2.1.\n */\nexport type CreateStoreOptions<\n T extends object,\n A extends object,\n G extends Record<string, Signal<unknown>>,\n> = {\n /** Display name. Used in error messages, devtools, and $id. */\n name?: string;\n /** Action factory — receives raw signals, returns methods exposed on the store. */\n actions?: (signals: StoreSignals<NoInfer<T>>) => A;\n /** Getter factory — receives raw signals, returns computed Signals exposed as ReadonlySignals. */\n getters?: (signals: StoreSignals<NoInfer<T>>) => G;\n /** Plugins to extend the store. Each receives the assembled store. */\n plugins?: ElurPlugin<NoInfer<T>, A, G>[];\n /**\n * Custom serializer for the store baseline (used by `$reset`).\n * Defaults to `structuredClone`. Provide this when your state contains\n * non-serializable values (Map, Set, class instances, etc.) that\n * `structuredClone` cannot handle.\n */\n serialize?: (state: NoInfer<T>) => T;\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst RESERVED = new Set([\n \"$id\", \"$state\", \"$stateSignal\", \"$snapshot\",\n \"$reset\", \"$patch\", \"$watch\", \"$dispose\",\n]);\n\nfunction assertKey(key: string): void {\n if (key === \"__proto__\" || key === \"constructor\" || key === \"prototype\") {\n throw new Error(`[elur] Store key \"${key}\" is not allowed for security reasons.`);\n }\n if (RESERVED.has(key)) throw new Error(`[elur] Store key \"${key}\" is reserved.`);\n}\n\nfunction warnReserved(key: string, ctx: \"action\" | \"getter\"): boolean {\n if (!RESERVED.has(key)) return true;\n console.warn(`[elur] Store ${ctx} \"${key}\" is reserved and will be ignored.`);\n return false;\n}\n\nfunction makeReadonly<T>(sig: Signal<T>, label: string): ReadonlySignal<T> {\n return new ReadonlySignal(sig, label);\n}\n\n// ---------------------------------------------------------------------------\n// createStore\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a reactive store with optional actions, getters, and plugins.\n *\n * @example Just state\n * ```ts\n * const counter = createStore({ count: 0 });\n * counter.count.value++;\n * ```\n *\n * @example State + actions\n * ```ts\n * const counter = createStore({ count: 0 }, {\n * actions: (s) => ({ increment: () => s.count.value++ }),\n * });\n * counter.increment();\n * ```\n *\n * @example State + getters (no actions)\n * ```ts\n * const inventory = createStore({ items: [] as Item[] }, {\n * getters: (s) => ({ count: computed(() => s.items.value.length) }),\n * });\n * inventory.count.value;\n * ```\n *\n * @example Full store\n * ```ts\n * const cart = createStore({ items: [] as Item[], discount: 0 }, {\n * name: \"cart\",\n * actions: (s) => ({ addItem: (i: Item) => { s.items.value = [...s.items.value, i] } }),\n * getters: (s) => ({ total: computed(() => s.items.value.reduce((sum, i) => sum + i.price, 0) * (1 - s.discount.value)) }),\n * plugins: [persistPlugin({ key: \"cart\" })],\n * });\n * ```\n */\nexport function createStore<\n T extends object,\n A extends object = Record<never, never>,\n G extends Record<string, Signal<unknown>> = Record<never, never>,\n>(\n initialState: T,\n options?: CreateStoreOptions<T, A, G>,\n): Store<T, A, G> {\n const { name = \"store\", actions: actionsFactory, getters: gettersFactory, plugins = [], serialize } = options ?? {};\n\n const keys = Object.keys(initialState) as Array<keyof T & string>;\n\n const signals = {} as { [K in keyof T]: Signal<T[K]> };\n for (const key of keys) {\n assertKey(key);\n signals[key] = signal(initialState[key]);\n }\n const typedSignals: StoreSignals<T> = signals;\n\n const _stateComputed = computed<T>(() => {\n const snap = {} as T;\n for (const key of keys) {\n snap[key] = signals[key].value;\n }\n return snap;\n });\n\n const $stateSignal = makeReadonly(_stateComputed, `store \"${name}\".$stateSignal`);\n\n let _baseline: T;\n try {\n // Use custom serializer if provided, otherwise structuredClone.\n _baseline = serialize ? serialize(initialState) : structuredClone(initialState);\n } catch (e) {\n throw new Error(\n `[elur] Store \"${name}\" initialState contains non-serializable data ` +\n `(functions, DOM nodes, Symbols, or WeakRefs). ` +\n `Provide a custom \\`serialize\\` option or remove these before creating the store. ` +\n `Original error: ${e}`\n );\n }\n\n const _guardFns: GuardFn<T>[] = [];\n\n function $reset(): void {\n const current = $snapshot();\n let next: Partial<T> = _baseline;\n for (const guard of _guardFns) {\n const result = guard(next, current);\n if (result !== undefined) {\n next = { ...next, ...result };\n }\n }\n batch(() => {\n for (const key of keys) {\n signals[key].value = (next as T)[key];\n }\n });\n }\n\n function $patch(partial: Partial<T>): void {\n let payload: Partial<T> = partial;\n const current = $snapshot();\n for (const guard of _guardFns) {\n const result = guard(payload, current);\n if (result !== undefined) {\n payload = result;\n }\n }\n batch(() => {\n for (const key of Object.keys(payload) as Array<keyof T & string>) {\n if (Object.prototype.hasOwnProperty.call(signals, key)) {\n signals[key].value = payload[key] as T[keyof T & string];\n }\n }\n });\n }\n\n function $snapshot(): T {\n // Passive read — uses peek() to avoid creating a reactive subscription.\n const snap = {} as T;\n for (const key of keys) {\n snap[key] = signals[key].peek();\n }\n return snap;\n }\n\n function $watch(\n cb: (next: T, prev: T | undefined) => void,\n opts?: WatchOptions,\n ): () => void {\n return watch(_stateComputed, cb, opts);\n }\n\n const store = Object.assign(\n Object.create(null) as object,\n typedSignals,\n { $reset, $patch, $watch, $snapshot },\n ) as Store<T, A, G>;\n\n Object.defineProperty(store, \"$id\", {\n value: name, writable: false, enumerable: false, configurable: false,\n });\n\n Object.defineProperty(store, \"$state\", {\n get(): T { return _stateComputed.value; },\n enumerable: true, configurable: false,\n });\n\n Object.defineProperty(store, \"$stateSignal\", {\n value: $stateSignal, writable: false, enumerable: false, configurable: false,\n });\n\n Object.defineProperty(store, \"_guardFns\", {\n value: _guardFns, writable: false, enumerable: false, configurable: false,\n });\n\n const occupiedKeys = new Set<string>([...keys, ...Array.from(RESERVED)]);\n\n if (actionsFactory) {\n const raw = actionsFactory(typedSignals);\n for (const key of Object.keys(raw)) {\n if (!warnReserved(key, \"action\")) continue;\n if (occupiedKeys.has(key)) {\n console.warn(\n `[elur] Store \"${name}\": action \"${key}\" collides with an existing ` +\n `signal or getter and will be ignored.`,\n );\n continue;\n }\n occupiedKeys.add(key);\n (store as Record<string, unknown>)[key] =\n (raw as Record<string, unknown>)[key];\n }\n }\n\n if (gettersFactory) {\n const raw = gettersFactory(typedSignals);\n for (const key of Object.keys(raw)) {\n if (!warnReserved(key, \"getter\")) continue;\n if (occupiedKeys.has(key)) {\n console.warn(\n `[elur] Store \"${name}\": getter \"${key}\" collides with an existing ` +\n `signal or action and will be ignored.`,\n );\n continue;\n }\n\n const sig = (raw as Record<string, Signal<unknown>>)[key];\n\n if (!(sig instanceof Signal)) {\n throw new TypeError(\n `[elur] Store \"${name}\": getter \"${key}\" must return a Signal ` +\n `(wrap it with computed()). Got: ${typeof sig}`\n );\n }\n\n occupiedKeys.add(key);\n (store as Record<string, unknown>)[key] =\n makeReadonly(sig, `getter \"${key}\" in store \"${name}\"`);\n }\n }\n\n const cleanups: Array<() => void> = [() => _stateComputed.dispose()];\n\n for (const plugin of plugins) {\n try {\n const cleanup = plugin(store);\n if (typeof cleanup === \"function\") cleanups.push(cleanup);\n } catch (error) {\n console.error(\n `[elur] Plugin initialization failed for store \"${name}\":`,\n error\n );\n }\n }\n\n (store as Record<string, unknown>)[\"$dispose\"] = () => {\n for (const fn of cleanups) fn();\n };\n\n return store;\n}"],"mappings":"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"}
1
+ {"version":3,"file":"store.cjs","names":[],"sources":["../../src/elur/store.ts"],"sourcesContent":["import {\n Signal,\n signal,\n computed,\n batch,\n watch,\n type WatchOptions,\n} from \"./reactivity.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Maps a plain state object T into a record of Signals — one per key.\n * This is what the actions/getters factories receive as their argument.\n *\n * Given: { count: number, name: string }\n * Becomes: { count: Signal<number>, name: Signal<string> }\n */\nexport type StoreSignals<T extends object> = {\n readonly [K in keyof T]: Signal<T[K]>;\n};\n\n/**\n * A read-only Signal — extends Signal so it satisfies `instanceof Signal`\n * checks (used by `watch()`), but throws on any mutation attempt.\n */\nexport class ReadonlySignal<T> extends Signal<T> {\n private readonly label: string;\n constructor(source: Signal<T>, label: string = \"ReadonlySignal\") {\n super(source.peek());\n this.label = label;\n\n Object.defineProperty(this, \"value\", {\n get: () => source.value,\n set: () => { throw new Error(`[elur] \"${this.label}\" is read-only.`); },\n configurable: false,\n });\n\n this.update = () => { throw new Error(`[elur] \"${this.label}\" is read-only.`); };\n this.dispose = () => { throw new Error(`[elur] Cannot dispose \"${this.label}\" directly.`); };\n }\n}\n\n/**\n * Maps a getters factory result (a record of Signals) into a record of\n * read-only Signals exposed on the store.\n *\n * The Omit pattern enforces `readonly value` at the type level — TypeScript\n * blocks `getter.value = x` at compile time. The runtime ReadonlySignal class\n * also throws on mutation as defense in depth.\n */\nexport type StoreGetters<G extends Record<string, Signal<unknown>>> = {\n readonly [K in keyof G]: Omit<ReadonlySignal<G[K] extends Signal<infer V> ? V : never>, \"value\"> & {\n readonly value: G[K] extends Signal<infer V> ? V : never;\n };\n};\n\n/**\n * The full store type — combines reactive state signals, action methods,\n * computed getters (as ReadonlySignals), and the framework-level $-prefixed API.\n */\nexport type Store<\n T extends object,\n A extends object = Record<never, never>,\n G extends Record<string, Signal<unknown>> = Record<never, never>,\n> = StoreSignals<T> & A & StoreGetters<G> & {\n readonly $id: string;\n /** Reactive snapshot — reading inside effect/computed creates a subscription to the whole state. */\n readonly $state: T;\n /**\n * Passive snapshot — returns the current state values WITHOUT creating\n * a reactive subscription. Use this in plugins, loggers, persistence,\n * or anywhere you need a one-shot read.\n */\n $snapshot(): T;\n /**\n * The computed Signal that backs $state. Plugins receive this to\n * compose new reactive nodes on top.\n */\n readonly $stateSignal: ReadonlySignal<T>;\n /** Reset to initial values (batched). */\n $reset(): void;\n /** Partial update (batched). */\n $patch(partial: Partial<T>): void;\n /** Watches state changes. Equivalent to `watch(store.$stateSignal, cb, opts)`. */\n $watch(cb: (next: T, prev: T | undefined) => void, options?: WatchOptions): () => void;\n /** Disposes the store and runs all plugin cleanups. */\n $dispose(): void;\n};\n\n// ---------------------------------------------------------------------------\n// Factory types\n// ---------------------------------------------------------------------------\n\nexport type ActionsFactory<\n T extends object,\n A extends object,\n> = (signals: StoreSignals<T>) => A;\n\nexport type GettersFactory<\n T extends object,\n G extends Record<string, Signal<unknown>>,\n> = (signals: StoreSignals<T>) => G;\n\n// ---------------------------------------------------------------------------\n// Plugin type\n// ---------------------------------------------------------------------------\n\n/**\n * Function type for mutation guards.\n * Intercepts $patch and $reset to validate or transform state before it is applied.\n */\nexport type GuardFn<T extends object> = (\n next: Partial<T>,\n current: T,\n) => Partial<T> | void;\n\n/**\n * A ElurPlugin is a function that receives the assembled store and\n * optionally returns a cleanup function called on $dispose().\n *\n * There are NO lifecycle hooks. Plugins extend the signal graph directly\n * using the framework primitives:\n *\n * watch(store.$stateSignal, ...) — react to any state change\n * computed(() => store.someSignal.value) — derive new nodes\n * store.$snapshot() — passive read for logging/persistence\n */\nexport type ElurPlugin<\n T extends object,\n A extends object = Record<never, never>,\n G extends Record<string, Signal<unknown>> = Record<never, never>,\n> = (store: Store<T, A, G>) => (() => void) | void;\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\n/**\n * Options object for createStore. NoInfer<T> on factory parameters ensures\n * T is inferred ONLY from initialState, never from the factories — this is\n * what restores the type inference that broke in v2.2.1.\n */\nexport type CreateStoreOptions<\n T extends object,\n A extends object,\n G extends Record<string, Signal<unknown>>,\n> = {\n /** Display name. Used in error messages, devtools, and $id. */\n name?: string;\n /** Action factory — receives raw signals, returns methods exposed on the store. */\n actions?: (signals: StoreSignals<NoInfer<T>>) => A;\n /** Getter factory — receives raw signals, returns computed Signals exposed as ReadonlySignals. */\n getters?: (signals: StoreSignals<NoInfer<T>>) => G;\n /** Plugins to extend the store. Each receives the assembled store. */\n plugins?: ElurPlugin<NoInfer<T>, A, G>[];\n /**\n * Custom serializer for the store baseline (used by `$reset`).\n * Defaults to `structuredClone`. Provide this when your state contains\n * non-serializable values (Map, Set, class instances, etc.) that\n * `structuredClone` cannot handle.\n */\n serialize?: (state: NoInfer<T>) => T;\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst RESERVED = new Set([\n \"$id\", \"$state\", \"$stateSignal\", \"$snapshot\",\n \"$reset\", \"$patch\", \"$watch\", \"$dispose\",\n]);\n\nfunction assertKey(key: string): void {\n if (key === \"__proto__\" || key === \"constructor\" || key === \"prototype\") {\n throw new Error(`[elur] Store key \"${key}\" is not allowed for security reasons.`);\n }\n if (RESERVED.has(key)) throw new Error(`[elur] Store key \"${key}\" is reserved.`);\n}\n\nfunction warnReserved(key: string, ctx: \"action\" | \"getter\"): boolean {\n if (!RESERVED.has(key)) return true;\n console.warn(`[elur] Store ${ctx} \"${key}\" is reserved and will be ignored.`);\n return false;\n}\n\nfunction makeReadonly<T>(sig: Signal<T>, label: string): ReadonlySignal<T> {\n return new ReadonlySignal(sig, label);\n}\n\n// ---------------------------------------------------------------------------\n// createStore\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a reactive store with optional actions, getters, and plugins.\n *\n * @example Just state\n * ```ts\n * const counter = createStore({ count: 0 });\n * counter.count.value++;\n * ```\n *\n * @example State + actions\n * ```ts\n * const counter = createStore({ count: 0 }, {\n * actions: (s) => ({ increment: () => s.count.value++ }),\n * });\n * counter.increment();\n * ```\n *\n * @example State + getters (no actions)\n * ```ts\n * const inventory = createStore({ items: [] as Item[] }, {\n * getters: (s) => ({ count: computed(() => s.items.value.length) }),\n * });\n * inventory.count.value;\n * ```\n *\n * @example Full store\n * ```ts\n * const cart = createStore({ items: [] as Item[], discount: 0 }, {\n * name: \"cart\",\n * actions: (s) => ({ addItem: (i: Item) => { s.items.value = [...s.items.value, i] } }),\n * getters: (s) => ({ total: computed(() => s.items.value.reduce((sum, i) => sum + i.price, 0) * (1 - s.discount.value)) }),\n * plugins: [persistPlugin({ key: \"cart\" })],\n * });\n * ```\n */\nexport function createStore<\n T extends object,\n A extends object = Record<never, never>,\n G extends Record<string, Signal<unknown>> = Record<never, never>,\n>(\n initialState: T,\n options?: CreateStoreOptions<T, A, G>,\n): Store<T, A, G> {\n const { name = \"store\", actions: actionsFactory, getters: gettersFactory, plugins = [], serialize } = options ?? {};\n\n const keys = Object.keys(initialState) as Array<keyof T & string>;\n\n const signals = {} as { [K in keyof T]: Signal<T[K]> };\n for (const key of keys) {\n assertKey(key);\n signals[key] = signal(initialState[key]);\n }\n const typedSignals: StoreSignals<T> = signals;\n\n const _stateComputed = computed<T>(() => {\n const snap = {} as T;\n for (const key of keys) {\n snap[key] = signals[key].value;\n }\n return snap;\n });\n\n const $stateSignal = makeReadonly(_stateComputed, `store \"${name}\".$stateSignal`);\n\n let _baseline: T;\n try {\n // Use custom serializer if provided, otherwise structuredClone.\n _baseline = serialize ? serialize(initialState) : structuredClone(initialState);\n } catch (e) {\n throw new Error(\n `[elur] Store \"${name}\" initialState contains non-serializable data ` +\n `(functions, DOM nodes, Symbols, or WeakRefs). ` +\n `Provide a custom \\`serialize\\` option or remove these before creating the store. ` +\n `Original error: ${e}`\n );\n }\n\n const _guardFns: GuardFn<T>[] = [];\n\n function $reset(): void {\n const current = $snapshot();\n let next: Partial<T> = _baseline;\n for (const guard of _guardFns) {\n const result = guard(next, current);\n if (result !== undefined) {\n next = { ...next, ...result };\n }\n }\n batch(() => {\n for (const key of keys) {\n signals[key].value = (next as T)[key];\n }\n });\n }\n\n function $patch(partial: Partial<T>): void {\n let payload: Partial<T> = partial;\n const current = $snapshot();\n for (const guard of _guardFns) {\n const result = guard(payload, current);\n if (result !== undefined) {\n payload = result;\n }\n }\n batch(() => {\n for (const key of Object.keys(payload) as Array<keyof T & string>) {\n if (Object.prototype.hasOwnProperty.call(signals, key)) {\n signals[key].value = payload[key] as T[keyof T & string];\n }\n }\n });\n }\n\n function $snapshot(): T {\n // Passive read — uses peek() to avoid creating a reactive subscription.\n const snap = {} as T;\n for (const key of keys) {\n snap[key] = signals[key].peek();\n }\n return snap;\n }\n\n function $watch(\n cb: (next: T, prev: T | undefined) => void,\n opts?: WatchOptions,\n ): () => void {\n return watch(_stateComputed, cb, opts);\n }\n\n const store = Object.assign(\n Object.create(null) as object,\n typedSignals,\n { $reset, $patch, $watch, $snapshot },\n ) as Store<T, A, G>;\n\n Object.defineProperty(store, \"$id\", {\n value: name, writable: false, enumerable: false, configurable: false,\n });\n\n Object.defineProperty(store, \"$state\", {\n get(): T { return _stateComputed.value; },\n enumerable: true, configurable: false,\n });\n\n Object.defineProperty(store, \"$stateSignal\", {\n value: $stateSignal, writable: false, enumerable: false, configurable: false,\n });\n\n Object.defineProperty(store, \"_guardFns\", {\n value: _guardFns, writable: false, enumerable: false, configurable: false,\n });\n\n const occupiedKeys = new Set<string>([...keys, ...Array.from(RESERVED)]);\n\n if (actionsFactory) {\n const raw = actionsFactory(typedSignals);\n for (const key of Object.keys(raw)) {\n if (!warnReserved(key, \"action\")) continue;\n if (occupiedKeys.has(key)) {\n console.warn(\n `[elur] Store \"${name}\": action \"${key}\" collides with an existing ` +\n `signal or getter and will be ignored.`,\n );\n continue;\n }\n occupiedKeys.add(key);\n (store as Record<string, unknown>)[key] =\n (raw as Record<string, unknown>)[key];\n }\n }\n\n if (gettersFactory) {\n const raw = gettersFactory(typedSignals);\n for (const key of Object.keys(raw)) {\n if (!warnReserved(key, \"getter\")) continue;\n if (occupiedKeys.has(key)) {\n console.warn(\n `[elur] Store \"${name}\": getter \"${key}\" collides with an existing ` +\n `signal or action and will be ignored.`,\n );\n continue;\n }\n\n const sig = (raw as Record<string, Signal<unknown>>)[key];\n\n if (!(sig instanceof Signal)) {\n throw new TypeError(\n `[elur] Store \"${name}\": getter \"${key}\" must return a Signal ` +\n `(wrap it with computed()). Got: ${typeof sig}`\n );\n }\n\n occupiedKeys.add(key);\n (store as Record<string, unknown>)[key] =\n makeReadonly(sig, `getter \"${key}\" in store \"${name}\"`);\n }\n }\n\n const cleanups: Array<() => void> = [() => _stateComputed.dispose()];\n\n for (const plugin of plugins) {\n try {\n const cleanup = plugin(store);\n if (typeof cleanup === \"function\") cleanups.push(cleanup);\n } catch (error) {\n console.error(\n `[elur] Plugin initialization failed for store \"${name}\":`,\n error\n );\n }\n }\n\n (store as Record<string, unknown>)[\"$dispose\"] = () => {\n for (const fn of cleanups) fn();\n };\n\n return store;\n}"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { Signal as e, batch as t, computed as n, signal as r, watch as i } from "./signals.js";
1
+ import { Signal as e, batch as t, computed as n, signal as r, watch as i } from "./elur/next-2/reactivity.js";
2
2
  //#region src/elur/store.ts
3
3
  var a = class extends e {
4
4
  label;
@@ -1,2 +1,2 @@
1
- const e=require(`../../signals.cjs`),t=require(`./dom-write.cjs`),n=require(`./node-binding.cjs`),r=require(`./sanitize.cjs`);function i(e,t){t?e.style.display===`none`&&(e.style.display=``):e.style.display!==`none`&&(e.style.display=`none`)}function a(e){let t=e.lastIndexOf(`>`),n=e.lastIndexOf(`<`);if(n<=t)return{type:`node`};let i=e.slice(n+1),a=i.lastIndexOf(`=`);if(a===-1)return{type:`node`};let o=i.endsWith(`"`)||i.endsWith(`'`)||i[i.length-1]===`"`||i[i.length-1]===`'`,s=a-1;for(;s>=0&&/\S/.test(i[s]);)s--;s++;let c=i.slice(s,a);if(c[0]===`@`){let e=c.slice(1).split(`.`);return{type:`event`,eventName:e[0],modifiers:e.slice(1),hadOpenQuote:o}}return{type:`attr`,attrName:c,hadOpenQuote:o,url:r.isUrlAttrName(c),executable:r.isExecutableAttrName(c)}}var o={enter:`Enter`,escape:`Escape`,space:` `,tab:`Tab`,delete:`Delete`,backspace:`Backspace`,up:`ArrowUp`,down:`ArrowDown`,left:`ArrowLeft`,right:`ArrowRight`},s=new Set([`click`,`dblclick`,`mousedown`,`mouseup`,`keydown`,`keyup`,`input`,`change`,`submit`]),c=new Set;function l(e,t,n){let r=e.target,i=e.stopPropagation,a=!1;for(e.stopPropagation=()=>{a=!0,i.call(e)};r&&r!==document;){let i=r[t];if(i){let t=r[n];if(t){if(t.includes(`prevent`)&&e.preventDefault(),t.includes(`stop`)&&e.stopPropagation(),t.includes(`self`)&&e.target!==r){r=r.parentNode;continue}if(`key`in e){let n=e,i=!0;for(let e of t){let t=o[e];if(t!==void 0&&n.key!==t){i=!1;break}if(!t&&e.length===1&&n.key.toLowerCase()!==e){i=!1;break}}if(!i){r=r.parentNode;continue}}}if(i(e),a)break}r=r.parentNode}e.stopPropagation=i}var u=new Map;function d(e){if(!c.has(e)){let t=`__elur_${e}`,n=`__elur_${e}_mods`,r=e=>l(e,t,n);u.set(e,r),document.addEventListener(e,r),c.add(e)}}function f(e,t,n,r){d(t);let i=`__elur_${t}`,a=`__elur_${t}_mods`;e[i]=r,n.length>0&&(e[a]=n)}function p(e,t,n,r){f(e,t,n,r);let i=`__elur_${t}`,a=`__elur_${t}_mods`;return()=>{e[i]=null,e[a]=null}}function m(e){return s.has(e)}function h(e,t,n,r){let i=Array(t.length),a=-1;for(let e=0;e<t.length;e++)r[e]&&r[e].nodeIndex>a&&(a=r[e].nodeIndex);let o=Array(a+1);if(o[0]=e,a>0){let t=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_COMMENT),n=1,r;for(;n<=a&&(r=t.nextNode());)o[n++]=r}for(let e=0;e<t.length;e++){let t=r[e];i[e]=t?o[t.nodeIndex]:null}return g(e,t,n,r,i)}function g(i,a,o,s,c){let l=[],u=[];for(let i=0;i<a.length;i++){let d=a[i],f=o[i],h=s[i];if(!h)continue;let g=c[i];if(d.type===`event`){let e=h.name,t=f,n=d.modifiers;if(m(e)&&!n.includes(`capture`)&&!n.includes(`once`)&&!n.includes(`passive`))l.push(p(g,e,n,t));else{let r={once:n.includes(`once`),capture:n.includes(`capture`),passive:n.includes(`passive`)},i=e=>{n.includes(`prevent`)&&e.preventDefault(),n.includes(`stop`)&&e.stopPropagation(),!(n.includes(`self`)&&e.target!==e.currentTarget)&&t(e)};g.addEventListener(e,i,r),l.push(()=>g.removeEventListener(e,i,r))}continue}if(d.type===`attr`){let n=h.name,i=g;if(n===`ref`){f.el=i,l.push(()=>{f.el=null});continue}if(n===`show`||n===`hide`){let r=i,a=null;if(typeof f==`function`){let i=!1,o=!1,s=!0,c=e.effect(()=>{o=!!f();let e=()=>{i=!1;let e=n===`show`?o:!o;a===null&&(a=r.style.display||``),r.style.display=e?a:`none`};s?(s=!1,e()):i||(i=!0,t.queueDOMWrite(e))});l.push(c)}else (n===`show`?f:!f)||(r.style.display=`none`);continue}(d.executable??r.isExecutableAttrName(n))&&console.warn(`[elur] Dynamic binding on executable attribute "${n}". Use @event for handlers; avoid binding untrusted values here.`);let a=d.url??r.isUrlAttrName(n),o=(n===`value`||n===`checked`||n===`selected`)&&n in i;if(typeof f==`function`){let s=!1,c,u=!0,d=e.effect(()=>{c=f();let e=()=>{s=!1;let e=c;if(o)i[n]=e??``;else if(e==null||e===!1)i.removeAttribute(n);else{let t=String(e);i.setAttribute(n,a?r.sanitizeUrl(t):t)}};u?(u=!1,e()):s||(s=!0,t.queueDOMWrite(e))});l.push(d)}else if(o)i[n]=f??``;else if(f!=null&&f!==!1){let e=String(f);i.setAttribute(n,a?r.sanitizeUrl(e):e)}continue}let _=g;if(!_)continue;let v=document.createTextNode(``);_.parentNode.replaceChild(v,_),n.activateNodeBinding(v,f,l,u)}return{disposes:l,postMountHooks:u}}exports._activateBindingsWithNodes=g,exports._ensureDelegatedEvent=d,exports._setDelegatedEvent=f,exports.activateBindings=h,exports.activateDelegatedEvent=p,exports.detectContext=a,exports.isDelegableEvent=m,exports.showWhen=i;
1
+ const e=require(`../next-2/reactivity.cjs`),t=require(`./types.cjs`),n=require(`../next-2/dom-write.cjs`),r=require(`../next-2/node-binding.cjs`),i=require(`./sanitize.cjs`);function a(e,t){t?e.style.display===`none`&&(e.style.display=``):e.style.display!==`none`&&(e.style.display=`none`)}function o(e){let t=e.lastIndexOf(`>`),n=e.lastIndexOf(`<`);if(n<=t)return{type:`node`};let r=e.slice(n+1),a=r.lastIndexOf(`=`);if(a===-1)return{type:`node`};let o=r.endsWith(`"`)||r.endsWith(`'`)||r[r.length-1]===`"`||r[r.length-1]===`'`,s=a-1;for(;s>=0&&/\S/.test(r[s]);)s--;s++;let c=r.slice(s,a);if(c[0]===`@`){let e=c.slice(1).split(`.`);return{type:`event`,eventName:e[0],modifiers:e.slice(1),hadOpenQuote:o}}return{type:`attr`,attrName:c,hadOpenQuote:o,url:i.isUrlAttrName(c),executable:i.isExecutableAttrName(c)}}var s={enter:`Enter`,escape:`Escape`,space:` `,tab:`Tab`,delete:`Delete`,backspace:`Backspace`,up:`ArrowUp`,down:`ArrowDown`,left:`ArrowLeft`,right:`ArrowRight`},c=new Set([`click`,`dblclick`,`mousedown`,`mouseup`,`keydown`,`keyup`,`input`,`change`,`submit`]),l=new Set;function u(e,t,n){let r=e.target,i=e.stopPropagation,a=!1;for(e.stopPropagation=()=>{a=!0,i.call(e)};r&&r!==document;){let i=r[t];if(i){let t=r[n];if(t){if(t.includes(`prevent`)&&e.preventDefault(),t.includes(`stop`)&&e.stopPropagation(),t.includes(`self`)&&e.target!==r){r=r.parentNode;continue}if(`key`in e){let n=e,i=!0;for(let e of t){let t=s[e];if(t!==void 0&&n.key!==t){i=!1;break}if(!t&&e.length===1&&n.key.toLowerCase()!==e){i=!1;break}}if(!i){r=r.parentNode;continue}}}if(i(e),a)break}r=r.parentNode}e.stopPropagation=i}var d=new Map;function f(e){if(!l.has(e)){let t=`__elur_${e}`,n=`__elur_${e}_mods`,r=e=>u(e,t,n);d.set(e,r),document.addEventListener(e,r),l.add(e)}}function p(e,t,n,r){f(t);let i=`__elur_${t}`,a=`__elur_${t}_mods`;e[i]=r,n.length>0&&(e[a]=n)}function m(e,t,n,r){p(e,t,n,r);let i=`__elur_${t}`,a=`__elur_${t}_mods`;return()=>{e[i]=null,e[a]=null}}function h(e){return c.has(e)}function g(e,t,n,r){let i=Array(t.length),a=-1;for(let e=0;e<t.length;e++)r[e]&&r[e].nodeIndex>a&&(a=r[e].nodeIndex);let o=Array(a+1);if(o[0]=e,a>0){let t=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_COMMENT),n=1,r;for(;n<=a&&(r=t.nextNode());)o[n++]=r}for(let e=0;e<t.length;e++){let t=r[e];i[e]=t?o[t.nodeIndex]:null}return _(e,t,n,r,i)}function _(a,o,s,c,l){let u=[],d=[];for(let a=0;a<o.length;a++){let f=o[a],p=s[a];t.isDerivedBinding(p)&&(p=p.get);let g=c[a];if(!g)continue;let _=l[a];if(f.type===`event`){let e=g.name,t=p,n=f.modifiers;if(h(e)&&!n.includes(`capture`)&&!n.includes(`once`)&&!n.includes(`passive`))u.push(m(_,e,n,t));else{let r={once:n.includes(`once`),capture:n.includes(`capture`),passive:n.includes(`passive`)},i=e=>{n.includes(`prevent`)&&e.preventDefault(),n.includes(`stop`)&&e.stopPropagation(),!(n.includes(`self`)&&e.target!==e.currentTarget)&&t(e)};_.addEventListener(e,i,r),u.push(()=>_.removeEventListener(e,i,r))}continue}if(f.type===`attr`){let t=g.name,r=_;if(t===`ref`){p.el=r,u.push(()=>{p.el=null});continue}if(t===`show`||t===`hide`){let i=r,a=null;if(typeof p==`function`){let r=!1,o=!1,s=!0,c=e.effect(()=>{o=!!p();let e=()=>{r=!1;let e=t===`show`?o:!o;a===null&&(a=i.style.display||``),i.style.display=e?a:`none`};s?(s=!1,e()):r||(r=!0,n.queueDOMWrite(e))});u.push(c)}else (t===`show`?p:!p)||(i.style.display=`none`);continue}(f.executable??i.isExecutableAttrName(t))&&console.warn(`[elur] Dynamic binding on executable attribute "${t}". Use @event for handlers; avoid binding untrusted values here.`);let a=f.url??i.isUrlAttrName(t),o=(t===`value`||t===`checked`||t===`selected`)&&t in r;if(p instanceof e.Signal){u.push(e._bindSignal(p,e=>{if(o)r[t]=e??``;else if(e==null||e===!1)r.removeAttribute(t);else{let n=String(e);r.setAttribute(t,a?i.sanitizeUrl(n):n)}}));continue}if(typeof p==`function`){let s=!1,c,l=!0,d=e.effect(()=>{c=p();let e=()=>{s=!1;let e=c;if(o)r[t]=e??``;else if(e==null||e===!1)r.removeAttribute(t);else{let n=String(e);r.setAttribute(t,a?i.sanitizeUrl(n):n)}};l?(l=!1,e()):s||(s=!0,n.queueDOMWrite(e))});u.push(d)}else if(o)r[t]=p??``;else if(p!=null&&p!==!1){let e=String(p);r.setAttribute(t,a?i.sanitizeUrl(e):e)}continue}let v=_;if(!v)continue;let y=document.createTextNode(``);v.parentNode.replaceChild(y,v),r.activateNodeBinding(y,p,u,d)}return{disposes:u,postMountHooks:d}}exports._activateBindingsWithNodes=_,exports._ensureDelegatedEvent=f,exports._setDelegatedEvent=p,exports.activateBindings=g,exports.activateDelegatedEvent=m,exports.detectContext=o,exports.isDelegableEvent=h,exports.showWhen=a;
2
2
  //# sourceMappingURL=bindings.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"bindings.cjs","names":[],"sources":["../../../../src/elur/template/bindings.ts"],"sourcesContent":["import { effect } from \"../reactivity.js\";\nimport type { ElurRef, TemplateBindingContext } from \"./types.js\";\nimport { activateNodeBinding } from \"./node-binding.js\";\nimport { queueDOMWrite } from \"./dom-write.js\";\nimport { isUrlAttrName, isExecutableAttrName, sanitizeUrl } from \"./sanitize.js\";\n\n// =============================================================================\n// --- show / hide ---\n// =============================================================================\n\n/** Toggles element visibility via `display: none` without unmounting. */\nexport function showWhen(el: HTMLElement, condition: boolean): void {\n if (!condition) {\n if (el.style.display !== \"none\") el.style.display = \"none\";\n } else {\n if (el.style.display === \"none\") el.style.display = \"\";\n }\n}\n\n// =============================================================================\n// --- Binding context ---\n// =============================================================================\n\nexport type BindingContext = TemplateBindingContext;\n\n/**\n * Determines the binding context (node, event, or attribute) for an interpolated\n * value based on the preceding template string.\n */\nexport function detectContext(prevString: string): BindingContext {\n const lastClose = prevString.lastIndexOf(\">\");\n const lastOpen = prevString.lastIndexOf(\"<\");\n\n if (lastOpen <= lastClose) {\n return { type: \"node\" };\n }\n\n const tagContent = prevString.slice(lastOpen + 1);\n\n const eqIdx = tagContent.lastIndexOf(\"=\");\n if (eqIdx === -1) {\n return { type: \"node\" };\n }\n\n const hadOpenQuote =\n tagContent.endsWith('\"') ||\n tagContent.endsWith(\"'\") ||\n tagContent[tagContent.length - 1] === '\"' ||\n tagContent[tagContent.length - 1] === \"'\";\n\n let startIdx = eqIdx - 1;\n while (startIdx >= 0 && /\\S/.test(tagContent[startIdx])) {\n startIdx--;\n }\n startIdx++;\n\n const fullAttr = tagContent.slice(startIdx, eqIdx);\n\n if (fullAttr[0] === \"@\") {\n const parts = fullAttr.slice(1).split(\".\");\n return {\n type: \"event\",\n eventName: parts[0],\n modifiers: parts.slice(1),\n hadOpenQuote,\n };\n }\n\n return {\n type: \"attr\",\n attrName: fullAttr,\n hadOpenQuote,\n // Precomputed once per template (compile time). Read as a cheap boolean\n // in the render/update hot path.\n url: isUrlAttrName(fullAttr),\n executable: isExecutableAttrName(fullAttr),\n };\n}\n\n// =============================================================================\n// --- Keyboard modifier map ---\n// =============================================================================\n\nconst KEY_MAP: Readonly<Record<string, string>> = {\n enter: \"Enter\",\n escape: \"Escape\",\n space: \" \",\n tab: \"Tab\",\n delete: \"Delete\",\n backspace: \"Backspace\",\n up: \"ArrowUp\",\n down: \"ArrowDown\",\n left: \"ArrowLeft\",\n right: \"ArrowRight\",\n};\n\n// =============================================================================\n// --- Global Event Delegation ---\n// =============================================================================\n\nconst DELEGABLE_EVENTS = new Set([\n \"click\", \"dblclick\", \"mousedown\", \"mouseup\",\n \"keydown\", \"keyup\", \"input\", \"change\", \"submit\"\n]);\nconst _delegatedRegistry = new Set<string>();\n\nfunction _globalEventHandlerCore(e: Event, propName: string, modsName: string): void {\n let target = e.target as Node | null;\n\n const originalStop = e.stopPropagation;\n let stopped = false;\n e.stopPropagation = () => {\n stopped = true;\n originalStop.call(e);\n };\n\n while (target && target !== document) {\n const handler = (target as any)[propName] as EventListener | undefined;\n if (handler) {\n const mods = (target as any)[modsName] as string[] | undefined;\n if (mods) {\n if (mods.includes(\"prevent\")) e.preventDefault();\n if (mods.includes(\"stop\")) e.stopPropagation();\n if (mods.includes(\"self\") && e.target !== target) {\n target = target.parentNode;\n continue;\n }\n if (\"key\" in e) {\n const ke = e as KeyboardEvent;\n let keyMatch = true;\n for (const mod of mods) {\n const mapped = KEY_MAP[mod];\n if (mapped !== undefined && ke.key !== mapped) { keyMatch = false; break; }\n if (!mapped && mod.length === 1 && ke.key.toLowerCase() !== mod) { keyMatch = false; break; }\n }\n if (!keyMatch) {\n target = target.parentNode;\n continue;\n }\n }\n }\n handler(e);\n if (stopped) break;\n }\n target = target.parentNode;\n }\n\n e.stopPropagation = originalStop;\n}\n\n// NOTE: entries are intentionally permanent — delegated listeners on document\n// live for the application lifetime.\nconst _delegatedHandlers = new Map<string, (e: Event) => void>();\n\n/**\n * Activates a delegated event on an element, using the same global registry\n * as mount-time bindings. Used by both `activateBindings` (mount) and the\n * hydrator to ensure consistent event delegation.\n *\n * @returns A dispose function that removes the handler from the element.\n */\nexport function _ensureDelegatedEvent(eventName: string): void {\n if (!_delegatedRegistry.has(eventName)) {\n const propName = `__elur_${eventName}`;\n const modsName = `__elur_${eventName}_mods`;\n const boundHandler = (e: Event) => _globalEventHandlerCore(e, propName, modsName);\n _delegatedHandlers.set(eventName, boundHandler);\n document.addEventListener(eventName, boundHandler);\n _delegatedRegistry.add(eventName);\n }\n}\n\nexport function _setDelegatedEvent(\n el: Element,\n eventName: string,\n modifiers: readonly string[],\n rawHandler: EventListener,\n): void {\n _ensureDelegatedEvent(eventName);\n const nodePropName = `__elur_${eventName}`;\n const nodeModsName = `__elur_${eventName}_mods`;\n (el as any)[nodePropName] = rawHandler;\n if (modifiers.length > 0) (el as any)[nodeModsName] = modifiers;\n}\n\nexport function activateDelegatedEvent(\n el: Element,\n eventName: string,\n modifiers: readonly string[],\n rawHandler: EventListener,\n): () => void {\n _setDelegatedEvent(el, eventName, modifiers, rawHandler);\n const nodePropName = `__elur_${eventName}`;\n const nodeModsName = `__elur_${eventName}_mods`;\n return () => {\n (el as any)[nodePropName] = null;\n (el as any)[nodeModsName] = null;\n };\n}\n\n/** Returns true if an event name is in the delegable set. */\nexport function isDelegableEvent(eventName: string): boolean {\n return DELEGABLE_EVENTS.has(eventName);\n}\n\n// =============================================================================\n// --- Binding activation ---\n// =============================================================================\n\n/** Activates all bindings on the cloned fragment. Returns dispose/postMount. */\nexport function activateBindings(\n fragment: DocumentFragment,\n contexts: BindingContext[],\n values: unknown[],\n pathMap: Array<{ nodeIndex: number; name?: string } | null>,\n): { disposes: Array<() => void>; postMountHooks: Array<() => void> } {\n // PHASE 1: READ — single-pass TreeWalker O(N)\n const resolvedNodes = new Array<Node | null>(contexts.length);\n\n let maxNodeIndex = -1;\n for (let i = 0; i < contexts.length; i++) {\n if (pathMap[i] && pathMap[i]!.nodeIndex > maxNodeIndex) {\n maxNodeIndex = pathMap[i]!.nodeIndex;\n }\n }\n\n const flatNodes = new Array<Node>(maxNodeIndex + 1);\n flatNodes[0] = fragment;\n if (maxNodeIndex > 0) {\n const walker = document.createTreeWalker(fragment, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT);\n let fi = 1;\n let currentNode: Node | null;\n while (fi <= maxNodeIndex && (currentNode = walker.nextNode())) {\n flatNodes[fi++] = currentNode;\n }\n }\n\n for (let i = 0; i < contexts.length; i++) {\n const info = pathMap[i];\n resolvedNodes[i] = info ? flatNodes[info.nodeIndex] : null;\n }\n\n // PHASE 2: MUTATE (delegated to _activateBindingsWithNodes)\n return _activateBindingsWithNodes(fragment, contexts, values, pathMap, resolvedNodes);\n}\n\n/**\n * Activates bindings using pre-resolved nodes — skips the TreeWalker phase.\n * Used by the compiler's __elurCompiledTemplate to eliminate the second TreeWalker.\n */\nexport function _activateBindingsWithNodes(\n _fragment: DocumentFragment,\n contexts: BindingContext[],\n values: unknown[],\n pathMap: Array<{ nodeIndex: number; name?: string } | null>,\n resolvedNodes: Array<Node | null>,\n): { disposes: Array<() => void>; postMountHooks: Array<() => void> } {\n const disposes: Array<() => void> = [];\n const postMountHooks: Array<() => void> = [];\n\n for (let i = 0; i < contexts.length; i++) {\n const ctx = contexts[i];\n const value = values[i];\n const info = pathMap[i];\n if (!info) continue;\n\n const el = resolvedNodes[i]!;\n\n // --- Events ---\n if (ctx.type === \"event\") {\n const eventName = info.name!;\n const rawHandler = value as EventListener;\n const mods = ctx.modifiers;\n\n const canDelegate =\n isDelegableEvent(eventName) &&\n !mods.includes(\"capture\") &&\n !mods.includes(\"once\") &&\n !mods.includes(\"passive\");\n\n if (canDelegate) {\n disposes.push(activateDelegatedEvent(el as Element, eventName, mods, rawHandler));\n } else {\n const listenerOpts: AddEventListenerOptions = {\n once: mods.includes(\"once\"),\n capture: mods.includes(\"capture\"),\n passive: mods.includes(\"passive\")\n };\n const handler = (e: Event) => {\n if (mods.includes(\"prevent\")) e.preventDefault();\n if (mods.includes(\"stop\")) e.stopPropagation();\n if (mods.includes(\"self\") && e.target !== e.currentTarget) return;\n rawHandler(e);\n };\n el.addEventListener(eventName, handler, listenerOpts);\n disposes.push(() => el.removeEventListener(eventName, handler, listenerOpts));\n }\n continue;\n }\n\n // --- Attributes ---\n if (ctx.type === \"attr\") {\n const attrName = info.name!;\n const element = el as Element;\n\n if (attrName === \"ref\") {\n (value as ElurRef<Element>).el = element;\n disposes.push(() => { (value as ElurRef<Element>).el = null; });\n continue;\n }\n\n if (attrName === \"show\" || attrName === \"hide\") {\n const htmlEl = element as HTMLElement;\n let originalDisplay: string | null = null;\n\n if (typeof value === \"function\") {\n let queued = false;\n let pendingVisible = false;\n let isFirstRun = true;\n\n const dispose = effect(() => {\n pendingVisible = Boolean((value as () => unknown)());\n const update = () => {\n queued = false;\n const shouldShow = attrName === \"show\" ? pendingVisible : !pendingVisible;\n if (originalDisplay === null) {\n originalDisplay = htmlEl.style.display || \"\";\n }\n htmlEl.style.display = shouldShow ? originalDisplay : \"none\";\n };\n\n if (isFirstRun) {\n isFirstRun = false;\n update();\n } else if (!queued) {\n queued = true;\n queueDOMWrite(update);\n }\n });\n disposes.push(dispose);\n } else {\n const shouldShow = attrName === \"show\" ? Boolean(value) : !Boolean(value);\n if (!shouldShow) htmlEl.style.display = \"none\";\n }\n continue;\n }\n\n // on*/srcdoc bindings are non-idiomatic in Elur (events use @click) and\n // turn an untrusted value into executable code. Warn the developer but\n // do not block — the attribute name is developer-authored.\n if (ctx.executable ?? isExecutableAttrName(attrName)) {\n console.warn(\n `[elur] Dynamic binding on executable attribute \"${attrName}\". Use @event for handlers; avoid binding untrusted values here.`,\n );\n }\n\n // Precomputed at compile time. Only URL attributes pay the sanitizer;\n // class/style/aria-*/data-*/custom attributes skip it entirely.\n const isUrl = ctx.url ?? isUrlAttrName(attrName);\n\n const isDomProp = (attrName === \"value\" || attrName === \"checked\" || attrName === \"selected\") && attrName in element;\n\n if (typeof value === \"function\") {\n let queued = false;\n let pendingValue: unknown;\n let isFirstRun = true;\n\n const dispose = effect(() => {\n pendingValue = (value as () => unknown)();\n const update = () => {\n queued = false;\n const v = pendingValue;\n if (isDomProp) {\n (element as any)[attrName] = v ?? \"\";\n } else if (v == null || v === false) {\n element.removeAttribute(attrName);\n } else {\n const s = String(v);\n element.setAttribute(attrName, isUrl ? sanitizeUrl(s) : s);\n }\n };\n\n if (isFirstRun) {\n isFirstRun = false;\n update();\n } else if (!queued) {\n queued = true;\n queueDOMWrite(update);\n }\n });\n disposes.push(dispose);\n } else {\n if (isDomProp) {\n (element as any)[attrName] = value ?? \"\";\n } else if (value != null && value !== false) {\n const s = String(value);\n element.setAttribute(attrName, isUrl ? sanitizeUrl(s) : s);\n }\n }\n continue;\n }\n\n // --- Nodes — delegate to node-binding.ts ---\n const originalAnchor = el as Comment;\n if (!originalAnchor) continue;\n\n const anchor = document.createTextNode(\"\");\n originalAnchor.parentNode!.replaceChild(anchor, originalAnchor);\n\n activateNodeBinding(anchor, value, disposes, postMountHooks);\n }\n\n return { disposes, postMountHooks };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"bindings.cjs","names":[],"sources":["../../../../src/elur/template/bindings.ts"],"sourcesContent":["import { effect, Signal, _bindSignal } from \"../reactivity.js\";\nimport type { ElurRef, TemplateBindingContext } from \"./types.js\";\nimport { isDerivedBinding } from \"./types.js\";\nimport { activateNodeBinding } from \"./node-binding.js\";\nimport { queueDOMWrite } from \"./dom-write.js\";\nimport { isUrlAttrName, isExecutableAttrName, sanitizeUrl } from \"./sanitize.js\";\n\n// =============================================================================\n// --- show / hide ---\n// =============================================================================\n\n/** Toggles element visibility via `display: none` without unmounting. */\nexport function showWhen(el: HTMLElement, condition: boolean): void {\n if (!condition) {\n if (el.style.display !== \"none\") el.style.display = \"none\";\n } else {\n if (el.style.display === \"none\") el.style.display = \"\";\n }\n}\n\n// =============================================================================\n// --- Binding context ---\n// =============================================================================\n\nexport type BindingContext = TemplateBindingContext;\n\n/**\n * Determines the binding context (node, event, or attribute) for an interpolated\n * value based on the preceding template string.\n */\nexport function detectContext(prevString: string): BindingContext {\n const lastClose = prevString.lastIndexOf(\">\");\n const lastOpen = prevString.lastIndexOf(\"<\");\n\n if (lastOpen <= lastClose) {\n return { type: \"node\" };\n }\n\n const tagContent = prevString.slice(lastOpen + 1);\n\n const eqIdx = tagContent.lastIndexOf(\"=\");\n if (eqIdx === -1) {\n return { type: \"node\" };\n }\n\n const hadOpenQuote =\n tagContent.endsWith('\"') ||\n tagContent.endsWith(\"'\") ||\n tagContent[tagContent.length - 1] === '\"' ||\n tagContent[tagContent.length - 1] === \"'\";\n\n let startIdx = eqIdx - 1;\n while (startIdx >= 0 && /\\S/.test(tagContent[startIdx])) {\n startIdx--;\n }\n startIdx++;\n\n const fullAttr = tagContent.slice(startIdx, eqIdx);\n\n if (fullAttr[0] === \"@\") {\n const parts = fullAttr.slice(1).split(\".\");\n return {\n type: \"event\",\n eventName: parts[0],\n modifiers: parts.slice(1),\n hadOpenQuote,\n };\n }\n\n return {\n type: \"attr\",\n attrName: fullAttr,\n hadOpenQuote,\n // Precomputed once per template (compile time). Read as a cheap boolean\n // in the render/update hot path.\n url: isUrlAttrName(fullAttr),\n executable: isExecutableAttrName(fullAttr),\n };\n}\n\n// =============================================================================\n// --- Keyboard modifier map ---\n// =============================================================================\n\nconst KEY_MAP: Readonly<Record<string, string>> = {\n enter: \"Enter\",\n escape: \"Escape\",\n space: \" \",\n tab: \"Tab\",\n delete: \"Delete\",\n backspace: \"Backspace\",\n up: \"ArrowUp\",\n down: \"ArrowDown\",\n left: \"ArrowLeft\",\n right: \"ArrowRight\",\n};\n\n// =============================================================================\n// --- Global Event Delegation ---\n// =============================================================================\n\nconst DELEGABLE_EVENTS = new Set([\n \"click\", \"dblclick\", \"mousedown\", \"mouseup\",\n \"keydown\", \"keyup\", \"input\", \"change\", \"submit\"\n]);\nconst _delegatedRegistry = new Set<string>();\n\nfunction _globalEventHandlerCore(e: Event, propName: string, modsName: string): void {\n let target = e.target as Node | null;\n\n const originalStop = e.stopPropagation;\n let stopped = false;\n e.stopPropagation = () => {\n stopped = true;\n originalStop.call(e);\n };\n\n while (target && target !== document) {\n const handler = (target as any)[propName] as EventListener | undefined;\n if (handler) {\n const mods = (target as any)[modsName] as string[] | undefined;\n if (mods) {\n if (mods.includes(\"prevent\")) e.preventDefault();\n if (mods.includes(\"stop\")) e.stopPropagation();\n if (mods.includes(\"self\") && e.target !== target) {\n target = target.parentNode;\n continue;\n }\n if (\"key\" in e) {\n const ke = e as KeyboardEvent;\n let keyMatch = true;\n for (const mod of mods) {\n const mapped = KEY_MAP[mod];\n if (mapped !== undefined && ke.key !== mapped) { keyMatch = false; break; }\n if (!mapped && mod.length === 1 && ke.key.toLowerCase() !== mod) { keyMatch = false; break; }\n }\n if (!keyMatch) {\n target = target.parentNode;\n continue;\n }\n }\n }\n handler(e);\n if (stopped) break;\n }\n target = target.parentNode;\n }\n\n e.stopPropagation = originalStop;\n}\n\n// NOTE: entries are intentionally permanent — delegated listeners on document\n// live for the application lifetime.\nconst _delegatedHandlers = new Map<string, (e: Event) => void>();\n\n/**\n * Activates a delegated event on an element, using the same global registry\n * as mount-time bindings. Used by both `activateBindings` (mount) and the\n * hydrator to ensure consistent event delegation.\n *\n * @returns A dispose function that removes the handler from the element.\n */\nexport function _ensureDelegatedEvent(eventName: string): void {\n if (!_delegatedRegistry.has(eventName)) {\n const propName = `__elur_${eventName}`;\n const modsName = `__elur_${eventName}_mods`;\n const boundHandler = (e: Event) => _globalEventHandlerCore(e, propName, modsName);\n _delegatedHandlers.set(eventName, boundHandler);\n document.addEventListener(eventName, boundHandler);\n _delegatedRegistry.add(eventName);\n }\n}\n\nexport function _setDelegatedEvent(\n el: Element,\n eventName: string,\n modifiers: readonly string[],\n rawHandler: EventListener,\n): void {\n _ensureDelegatedEvent(eventName);\n const nodePropName = `__elur_${eventName}`;\n const nodeModsName = `__elur_${eventName}_mods`;\n (el as any)[nodePropName] = rawHandler;\n if (modifiers.length > 0) (el as any)[nodeModsName] = modifiers;\n}\n\nexport function activateDelegatedEvent(\n el: Element,\n eventName: string,\n modifiers: readonly string[],\n rawHandler: EventListener,\n): () => void {\n _setDelegatedEvent(el, eventName, modifiers, rawHandler);\n const nodePropName = `__elur_${eventName}`;\n const nodeModsName = `__elur_${eventName}_mods`;\n return () => {\n (el as any)[nodePropName] = null;\n (el as any)[nodeModsName] = null;\n };\n}\n\n/** Returns true if an event name is in the delegable set. */\nexport function isDelegableEvent(eventName: string): boolean {\n return DELEGABLE_EVENTS.has(eventName);\n}\n\n// =============================================================================\n// --- Binding activation ---\n// =============================================================================\n\n/** Activates all bindings on the cloned fragment. Returns dispose/postMount. */\nexport function activateBindings(\n fragment: DocumentFragment,\n contexts: BindingContext[],\n values: unknown[],\n pathMap: Array<{ nodeIndex: number; name?: string } | null>,\n): { disposes: Array<() => void>; postMountHooks: Array<() => void> } {\n // PHASE 1: READ — single-pass TreeWalker O(N)\n const resolvedNodes = new Array<Node | null>(contexts.length);\n\n let maxNodeIndex = -1;\n for (let i = 0; i < contexts.length; i++) {\n if (pathMap[i] && pathMap[i]!.nodeIndex > maxNodeIndex) {\n maxNodeIndex = pathMap[i]!.nodeIndex;\n }\n }\n\n const flatNodes = new Array<Node>(maxNodeIndex + 1);\n flatNodes[0] = fragment;\n if (maxNodeIndex > 0) {\n const walker = document.createTreeWalker(fragment, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT);\n let fi = 1;\n let currentNode: Node | null;\n while (fi <= maxNodeIndex && (currentNode = walker.nextNode())) {\n flatNodes[fi++] = currentNode;\n }\n }\n\n for (let i = 0; i < contexts.length; i++) {\n const info = pathMap[i];\n resolvedNodes[i] = info ? flatNodes[info.nodeIndex] : null;\n }\n\n // PHASE 2: MUTATE (delegated to _activateBindingsWithNodes)\n return _activateBindingsWithNodes(fragment, contexts, values, pathMap, resolvedNodes);\n}\n\n/**\n * Activates bindings using pre-resolved nodes — skips the TreeWalker phase.\n * Used by the compiler's __elurCompiledTemplate to eliminate the second TreeWalker.\n */\nexport function _activateBindingsWithNodes(\n _fragment: DocumentFragment,\n contexts: BindingContext[],\n values: unknown[],\n pathMap: Array<{ nodeIndex: number; name?: string } | null>,\n resolvedNodes: Array<Node | null>,\n): { disposes: Array<() => void>; postMountHooks: Array<() => void> } {\n const disposes: Array<() => void> = [];\n const postMountHooks: Array<() => void> = [];\n\n for (let i = 0; i < contexts.length; i++) {\n const ctx = contexts[i];\n let value = values[i];\n // C.7 T2: desenvolver el pack derivado a getter — cubre todos los\n // paths aguas abajo (attr, show/hide, prop, etc.) con tracking\n // dinámico — semántica idéntica al getter genérico.\n if (isDerivedBinding(value)) value = value.get;\n const info = pathMap[i];\n if (!info) continue;\n\n const el = resolvedNodes[i]!;\n\n // --- Events ---\n if (ctx.type === \"event\") {\n const eventName = info.name!;\n const rawHandler = value as EventListener;\n const mods = ctx.modifiers;\n\n const canDelegate =\n isDelegableEvent(eventName) &&\n !mods.includes(\"capture\") &&\n !mods.includes(\"once\") &&\n !mods.includes(\"passive\");\n\n if (canDelegate) {\n disposes.push(activateDelegatedEvent(el as Element, eventName, mods, rawHandler));\n } else {\n const listenerOpts: AddEventListenerOptions = {\n once: mods.includes(\"once\"),\n capture: mods.includes(\"capture\"),\n passive: mods.includes(\"passive\")\n };\n const handler = (e: Event) => {\n if (mods.includes(\"prevent\")) e.preventDefault();\n if (mods.includes(\"stop\")) e.stopPropagation();\n if (mods.includes(\"self\") && e.target !== e.currentTarget) return;\n rawHandler(e);\n };\n el.addEventListener(eventName, handler, listenerOpts);\n disposes.push(() => el.removeEventListener(eventName, handler, listenerOpts));\n }\n continue;\n }\n\n // --- Attributes ---\n if (ctx.type === \"attr\") {\n const attrName = info.name!;\n const element = el as Element;\n\n if (attrName === \"ref\") {\n (value as ElurRef<Element>).el = element;\n disposes.push(() => { (value as ElurRef<Element>).el = null; });\n continue;\n }\n\n if (attrName === \"show\" || attrName === \"hide\") {\n const htmlEl = element as HTMLElement;\n let originalDisplay: string | null = null;\n\n if (typeof value === \"function\") {\n let queued = false;\n let pendingVisible = false;\n let isFirstRun = true;\n\n const dispose = effect(() => {\n pendingVisible = Boolean((value as () => unknown)());\n const update = () => {\n queued = false;\n const shouldShow = attrName === \"show\" ? pendingVisible : !pendingVisible;\n if (originalDisplay === null) {\n originalDisplay = htmlEl.style.display || \"\";\n }\n htmlEl.style.display = shouldShow ? originalDisplay : \"none\";\n };\n\n if (isFirstRun) {\n isFirstRun = false;\n update();\n } else if (!queued) {\n queued = true;\n queueDOMWrite(update);\n }\n });\n disposes.push(dispose);\n } else {\n const shouldShow = attrName === \"show\" ? Boolean(value) : !Boolean(value);\n if (!shouldShow) htmlEl.style.display = \"none\";\n }\n continue;\n }\n\n // on*/srcdoc bindings are non-idiomatic in Elur (events use @click) and\n // turn an untrusted value into executable code. Warn the developer but\n // do not block — the attribute name is developer-authored.\n if (ctx.executable ?? isExecutableAttrName(attrName)) {\n console.warn(\n `[elur] Dynamic binding on executable attribute \"${attrName}\". Use @event for handlers; avoid binding untrusted values here.`,\n );\n }\n\n // Precomputed at compile time. Only URL attributes pay the sanitizer;\n // class/style/aria-*/data-*/custom attributes skip it entirely.\n const isUrl = ctx.url ?? isUrlAttrName(attrName);\n\n const isDomProp = (attrName === \"value\" || attrName === \"checked\" || attrName === \"selected\") && attrName in element;\n\n // C.6 T1: la señal viaja como valor — edge directa, sin getter.\n if (value instanceof Signal) {\n disposes.push(_bindSignal(value, (v) => {\n if (isDomProp) {\n (element as any)[attrName] = v ?? \"\";\n } else if (v == null || v === false) {\n element.removeAttribute(attrName);\n } else {\n const s = String(v);\n element.setAttribute(attrName, isUrl ? sanitizeUrl(s) : s);\n }\n }));\n continue;\n }\n\n if (typeof value === \"function\") {\n let queued = false;\n let pendingValue: unknown;\n let isFirstRun = true;\n\n const dispose = effect(() => {\n pendingValue = (value as () => unknown)();\n const update = () => {\n queued = false;\n const v = pendingValue;\n if (isDomProp) {\n (element as any)[attrName] = v ?? \"\";\n } else if (v == null || v === false) {\n element.removeAttribute(attrName);\n } else {\n const s = String(v);\n element.setAttribute(attrName, isUrl ? sanitizeUrl(s) : s);\n }\n };\n\n if (isFirstRun) {\n isFirstRun = false;\n update();\n } else if (!queued) {\n queued = true;\n queueDOMWrite(update);\n }\n });\n disposes.push(dispose);\n } else {\n if (isDomProp) {\n (element as any)[attrName] = value ?? \"\";\n } else if (value != null && value !== false) {\n const s = String(value);\n element.setAttribute(attrName, isUrl ? sanitizeUrl(s) : s);\n }\n }\n continue;\n }\n\n // --- Nodes — delegate to node-binding.ts ---\n const originalAnchor = el as Comment;\n if (!originalAnchor) continue;\n\n const anchor = document.createTextNode(\"\");\n originalAnchor.parentNode!.replaceChild(anchor, originalAnchor);\n\n activateNodeBinding(anchor, value, disposes, postMountHooks);\n }\n\n return { disposes, postMountHooks };\n}\n"],"mappings":"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"}