@elurjs/kit 2.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. package/CHANGELOG.md +874 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1330 -0
  4. package/bin/elur-kit.js +54 -0
  5. package/dist/lib/action/define.d.cts +72 -0
  6. package/dist/lib/action/define.d.ts +72 -0
  7. package/dist/lib/action/error-store.d.cts +29 -0
  8. package/dist/lib/action/error-store.d.ts +29 -0
  9. package/dist/lib/action/index.cjs +1 -0
  10. package/dist/lib/action/index.d.cts +64 -0
  11. package/dist/lib/action/index.d.ts +64 -0
  12. package/dist/lib/action/index.js +2 -0
  13. package/dist/lib/action/origin.d.cts +20 -0
  14. package/dist/lib/action/origin.d.ts +20 -0
  15. package/dist/lib/action/scan.d.cts +28 -0
  16. package/dist/lib/action/scan.d.ts +28 -0
  17. package/dist/lib/action/server.d.cts +30 -0
  18. package/dist/lib/action/server.d.ts +30 -0
  19. package/dist/lib/action-BB6N5yD-.js +111 -0
  20. package/dist/lib/action-BB6N5yD-.js.map +1 -0
  21. package/dist/lib/action-DZHihU3Z.cjs +2 -0
  22. package/dist/lib/action-DZHihU3Z.cjs.map +1 -0
  23. package/dist/lib/adapters/bun.cjs +30 -0
  24. package/dist/lib/adapters/bun.cjs.map +1 -0
  25. package/dist/lib/adapters/bun.d.cts +13 -0
  26. package/dist/lib/adapters/bun.d.ts +13 -0
  27. package/dist/lib/adapters/bun.js +65 -0
  28. package/dist/lib/adapters/bun.js.map +1 -0
  29. package/dist/lib/adapters/index.cjs +0 -0
  30. package/dist/lib/adapters/index.d.cts +30 -0
  31. package/dist/lib/adapters/index.d.ts +30 -0
  32. package/dist/lib/adapters/index.js +0 -0
  33. package/dist/lib/adapters/netlify.cjs +10 -0
  34. package/dist/lib/adapters/netlify.cjs.map +1 -0
  35. package/dist/lib/adapters/netlify.d.cts +13 -0
  36. package/dist/lib/adapters/netlify.d.ts +13 -0
  37. package/dist/lib/adapters/netlify.js +51 -0
  38. package/dist/lib/adapters/netlify.js.map +1 -0
  39. package/dist/lib/adapters/node.cjs +46 -0
  40. package/dist/lib/adapters/node.cjs.map +1 -0
  41. package/dist/lib/adapters/node.d.cts +13 -0
  42. package/dist/lib/adapters/node.d.ts +13 -0
  43. package/dist/lib/adapters/node.js +105 -0
  44. package/dist/lib/adapters/node.js.map +1 -0
  45. package/dist/lib/adapters/shared.d.cts +17 -0
  46. package/dist/lib/adapters/shared.d.ts +17 -0
  47. package/dist/lib/adapters/vercel.cjs +2 -0
  48. package/dist/lib/adapters/vercel.cjs.map +1 -0
  49. package/dist/lib/adapters/vercel.d.cts +9 -0
  50. package/dist/lib/adapters/vercel.d.ts +9 -0
  51. package/dist/lib/adapters/vercel.js +62 -0
  52. package/dist/lib/adapters/vercel.js.map +1 -0
  53. package/dist/lib/build/build.d.cts +83 -0
  54. package/dist/lib/build/build.d.ts +83 -0
  55. package/dist/lib/build/document-shell.d.cts +48 -0
  56. package/dist/lib/build/document-shell.d.ts +48 -0
  57. package/dist/lib/build/transform-source.d.cts +26 -0
  58. package/dist/lib/build/transform-source.d.ts +26 -0
  59. package/dist/lib/build/vite-build.d.cts +78 -0
  60. package/dist/lib/build/vite-build.d.ts +78 -0
  61. package/dist/lib/cache/policy.d.cts +25 -0
  62. package/dist/lib/cache/policy.d.ts +25 -0
  63. package/dist/lib/cache.d.cts +13 -0
  64. package/dist/lib/cache.d.ts +13 -0
  65. package/dist/lib/capabilities-C0TDQoMY.cjs +2 -0
  66. package/dist/lib/capabilities-C0TDQoMY.cjs.map +1 -0
  67. package/dist/lib/capabilities-C7txofmY.js +45 -0
  68. package/dist/lib/capabilities-C7txofmY.js.map +1 -0
  69. package/dist/lib/cli/commands.d.cts +15 -0
  70. package/dist/lib/cli/commands.d.ts +15 -0
  71. package/dist/lib/cli.cjs +5132 -0
  72. package/dist/lib/cli.cjs.map +1 -0
  73. package/dist/lib/cli.d.cts +30 -0
  74. package/dist/lib/cli.d.ts +30 -0
  75. package/dist/lib/cli.js +5131 -0
  76. package/dist/lib/cli.js.map +1 -0
  77. package/dist/lib/collections-Dbc8aRLf.js +233 -0
  78. package/dist/lib/collections-Dbc8aRLf.js.map +1 -0
  79. package/dist/lib/collections-VDy5g9p8.cjs +13 -0
  80. package/dist/lib/collections-VDy5g9p8.cjs.map +1 -0
  81. package/dist/lib/config/index.cjs +2 -0
  82. package/dist/lib/config/index.cjs.map +1 -0
  83. package/dist/lib/config/index.d.cts +100 -0
  84. package/dist/lib/config/index.d.ts +100 -0
  85. package/dist/lib/config/index.js +122 -0
  86. package/dist/lib/config/index.js.map +1 -0
  87. package/dist/lib/content/collections.d.cts +69 -0
  88. package/dist/lib/content/collections.d.ts +69 -0
  89. package/dist/lib/content/frontmatter.d.cts +20 -0
  90. package/dist/lib/content/frontmatter.d.ts +20 -0
  91. package/dist/lib/content/index.cjs +2 -0
  92. package/dist/lib/content/index.cjs.map +1 -0
  93. package/dist/lib/content/index.d.cts +5 -0
  94. package/dist/lib/content/index.d.ts +5 -0
  95. package/dist/lib/content/index.js +28 -0
  96. package/dist/lib/content/index.js.map +1 -0
  97. package/dist/lib/content/markdown.d.cts +6 -0
  98. package/dist/lib/content/markdown.d.ts +6 -0
  99. package/dist/lib/content/raw.d.cts +9 -0
  100. package/dist/lib/content/raw.d.ts +9 -0
  101. package/dist/lib/content/schema.d.cts +14 -0
  102. package/dist/lib/content/schema.d.ts +14 -0
  103. package/dist/lib/errors-0X7pCFNU.cjs +2 -0
  104. package/dist/lib/errors-0X7pCFNU.cjs.map +1 -0
  105. package/dist/lib/errors-ePYxDlhU.js +70 -0
  106. package/dist/lib/errors-ePYxDlhU.js.map +1 -0
  107. package/dist/lib/errors.d.cts +84 -0
  108. package/dist/lib/errors.d.ts +84 -0
  109. package/dist/lib/hydrate-8a3TbQbs.cjs +2 -0
  110. package/dist/lib/hydrate-8a3TbQbs.cjs.map +1 -0
  111. package/dist/lib/hydrate-CgZGrTxF.js +126 -0
  112. package/dist/lib/hydrate-CgZGrTxF.js.map +1 -0
  113. package/dist/lib/image/index.cjs +2 -0
  114. package/dist/lib/image/index.cjs.map +1 -0
  115. package/dist/lib/image/index.d.cts +37 -0
  116. package/dist/lib/image/index.d.ts +37 -0
  117. package/dist/lib/image/index.js +49 -0
  118. package/dist/lib/image/index.js.map +1 -0
  119. package/dist/lib/image/pipeline.d.cts +37 -0
  120. package/dist/lib/image/pipeline.d.ts +37 -0
  121. package/dist/lib/image/registry.cjs +2 -0
  122. package/dist/lib/image/registry.cjs.map +1 -0
  123. package/dist/lib/image/registry.d.cts +26 -0
  124. package/dist/lib/image/registry.d.ts +26 -0
  125. package/dist/lib/image/registry.js +23 -0
  126. package/dist/lib/image/registry.js.map +1 -0
  127. package/dist/lib/image/service.cjs +2 -0
  128. package/dist/lib/image/service.cjs.map +1 -0
  129. package/dist/lib/image/service.d.cts +164 -0
  130. package/dist/lib/image/service.d.ts +164 -0
  131. package/dist/lib/image/service.js +308 -0
  132. package/dist/lib/image/service.js.map +1 -0
  133. package/dist/lib/index.cjs +1 -0
  134. package/dist/lib/index.cjs.map +1 -0
  135. package/dist/lib/index.d.cts +38 -0
  136. package/dist/lib/index.d.ts +38 -0
  137. package/dist/lib/index.js +1 -0
  138. package/dist/lib/index.js.map +1 -0
  139. package/dist/lib/integrations/hooks.d.cts +58 -0
  140. package/dist/lib/integrations/hooks.d.ts +58 -0
  141. package/dist/lib/integrations/index.cjs +1 -0
  142. package/dist/lib/integrations/index.d.cts +18 -0
  143. package/dist/lib/integrations/index.d.ts +18 -0
  144. package/dist/lib/integrations/index.js +2 -0
  145. package/dist/lib/integrations-7dpN1RYB.js +35 -0
  146. package/dist/lib/integrations-7dpN1RYB.js.map +1 -0
  147. package/dist/lib/integrations-DDqfWjvH.cjs +2 -0
  148. package/dist/lib/integrations-DDqfWjvH.cjs.map +1 -0
  149. package/dist/lib/interpolation-plugin-Bt-KRKVY.js +365 -0
  150. package/dist/lib/interpolation-plugin-Bt-KRKVY.js.map +1 -0
  151. package/dist/lib/interpolation-plugin-CMPCDlVJ.cjs +31 -0
  152. package/dist/lib/interpolation-plugin-CMPCDlVJ.cjs.map +1 -0
  153. package/dist/lib/island/generate-entry.d.cts +27 -0
  154. package/dist/lib/island/generate-entry.d.ts +27 -0
  155. package/dist/lib/island/hydrate.d.cts +33 -0
  156. package/dist/lib/island/hydrate.d.ts +33 -0
  157. package/dist/lib/island/index.cjs +1 -0
  158. package/dist/lib/island/index.d.cts +2 -0
  159. package/dist/lib/island/index.d.ts +2 -0
  160. package/dist/lib/island/index.js +1 -0
  161. package/dist/lib/island/island.d.cts +32 -0
  162. package/dist/lib/island/island.d.ts +32 -0
  163. package/dist/lib/island/scan.d.cts +14 -0
  164. package/dist/lib/island/scan.d.ts +14 -0
  165. package/dist/lib/manifest/index.cjs +3 -0
  166. package/dist/lib/manifest/index.cjs.map +1 -0
  167. package/dist/lib/manifest/index.d.cts +18 -0
  168. package/dist/lib/manifest/index.d.ts +18 -0
  169. package/dist/lib/manifest/index.js +110 -0
  170. package/dist/lib/manifest/index.js.map +1 -0
  171. package/dist/lib/middleware/index.d.cts +56 -0
  172. package/dist/lib/middleware/index.d.ts +56 -0
  173. package/dist/lib/middleware/stream-boundary.d.cts +53 -0
  174. package/dist/lib/middleware/stream-boundary.d.ts +53 -0
  175. package/dist/lib/node-http-DRAUhO0c.js +617 -0
  176. package/dist/lib/node-http-DRAUhO0c.js.map +1 -0
  177. package/dist/lib/node-http-DhxguYyz.cjs +13 -0
  178. package/dist/lib/node-http-DhxguYyz.cjs.map +1 -0
  179. package/dist/lib/render/render-to-string.d.cts +12 -0
  180. package/dist/lib/render/render-to-string.d.ts +12 -0
  181. package/dist/lib/render/ssr-flag.d.cts +4 -0
  182. package/dist/lib/render/ssr-flag.d.ts +4 -0
  183. package/dist/lib/route-scanner-JY6YrNH5.cjs +2 -0
  184. package/dist/lib/route-scanner-JY6YrNH5.cjs.map +1 -0
  185. package/dist/lib/route-scanner-XcLId7lK.js +100 -0
  186. package/dist/lib/route-scanner-XcLId7lK.js.map +1 -0
  187. package/dist/lib/router/client.cjs +2 -0
  188. package/dist/lib/router/client.cjs.map +1 -0
  189. package/dist/lib/router/client.d.cts +47 -0
  190. package/dist/lib/router/client.d.ts +47 -0
  191. package/dist/lib/router/client.js +298 -0
  192. package/dist/lib/router/client.js.map +1 -0
  193. package/dist/lib/router/route-scanner.d.cts +50 -0
  194. package/dist/lib/router/route-scanner.d.ts +50 -0
  195. package/dist/lib/runtime/capabilities.d.cts +47 -0
  196. package/dist/lib/runtime/capabilities.d.ts +47 -0
  197. package/dist/lib/runtime/context.d.cts +140 -0
  198. package/dist/lib/runtime/context.d.ts +140 -0
  199. package/dist/lib/runtime/handler.d.cts +39 -0
  200. package/dist/lib/runtime/handler.d.ts +39 -0
  201. package/dist/lib/runtime/index.cjs +1 -0
  202. package/dist/lib/runtime/index.d.cts +6 -0
  203. package/dist/lib/runtime/index.d.ts +6 -0
  204. package/dist/lib/runtime/index.js +4 -0
  205. package/dist/lib/runtime/node-http.d.cts +2 -0
  206. package/dist/lib/runtime/node-http.d.ts +2 -0
  207. package/dist/lib/runtime/security-headers.d.cts +13 -0
  208. package/dist/lib/runtime/security-headers.d.ts +13 -0
  209. package/dist/lib/runtime/static.d.cts +1 -0
  210. package/dist/lib/runtime/static.d.ts +1 -0
  211. package/dist/lib/runtime-CrH5X-Es.cjs +2 -0
  212. package/dist/lib/runtime-CrH5X-Es.cjs.map +1 -0
  213. package/dist/lib/runtime-Ds2-wH6P.js +520 -0
  214. package/dist/lib/runtime-Ds2-wH6P.js.map +1 -0
  215. package/dist/lib/scan-CY2O9ty6.cjs +2 -0
  216. package/dist/lib/scan-CY2O9ty6.cjs.map +1 -0
  217. package/dist/lib/scan-CjMUgnsd.js +22 -0
  218. package/dist/lib/scan-CjMUgnsd.js.map +1 -0
  219. package/dist/lib/scan-DXzYtmRQ.cjs +2 -0
  220. package/dist/lib/scan-DXzYtmRQ.cjs.map +1 -0
  221. package/dist/lib/scan-Dn93ycXG.js +33 -0
  222. package/dist/lib/scan-Dn93ycXG.js.map +1 -0
  223. package/dist/lib/seo/index.cjs +10 -0
  224. package/dist/lib/seo/index.cjs.map +1 -0
  225. package/dist/lib/seo/index.d.cts +99 -0
  226. package/dist/lib/seo/index.d.ts +99 -0
  227. package/dist/lib/seo/index.js +41 -0
  228. package/dist/lib/seo/index.js.map +1 -0
  229. package/dist/lib/shared-CfqOLsKj.cjs +183 -0
  230. package/dist/lib/shared-CfqOLsKj.cjs.map +1 -0
  231. package/dist/lib/shared-DU3iQgr7.js +236 -0
  232. package/dist/lib/shared-DU3iQgr7.js.map +1 -0
  233. package/dist/lib/ssr/match.d.cts +25 -0
  234. package/dist/lib/ssr/match.d.ts +25 -0
  235. package/dist/lib/ssr/render.d.cts +62 -0
  236. package/dist/lib/ssr/render.d.ts +62 -0
  237. package/dist/lib/ssr/server.d.cts +41 -0
  238. package/dist/lib/ssr/server.d.ts +41 -0
  239. package/dist/lib/ssr/stream.d.cts +48 -0
  240. package/dist/lib/ssr/stream.d.ts +48 -0
  241. package/dist/lib/stream-CpOG052V.js +104 -0
  242. package/dist/lib/stream-CpOG052V.js.map +1 -0
  243. package/dist/lib/stream-DmZjYqHm.cjs +17 -0
  244. package/dist/lib/stream-DmZjYqHm.cjs.map +1 -0
  245. package/dist/lib/types.d.cts +97 -0
  246. package/dist/lib/types.d.ts +97 -0
  247. package/dist/lib/vite/index.cjs +2 -0
  248. package/dist/lib/vite/index.cjs.map +1 -0
  249. package/dist/lib/vite/index.d.cts +41 -0
  250. package/dist/lib/vite/index.d.ts +41 -0
  251. package/dist/lib/vite/index.js +229 -0
  252. package/dist/lib/vite/index.js.map +1 -0
  253. package/dist/lib/vite/interpolation-plugin.d.cts +64 -0
  254. package/dist/lib/vite/interpolation-plugin.d.ts +64 -0
  255. package/package.json +292 -0
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Represents a failed action result. Returned by `fail()` from server actions.
3
+ *
4
+ * The `__elur_js_action_failure` marker is set on the instance so the server can
5
+ * detect it even when the value crosses a bundling boundary (e.g. the CLI is
6
+ * bundled separately from the user's action modules).
7
+ */
8
+ export declare class ActionFailure<TData = unknown> {
9
+ status: number;
10
+ data: TData;
11
+ readonly __elur_js_action_failure = true;
12
+ constructor(status: number, data: TData);
13
+ }
14
+ /**
15
+ * Represents a redirect returned by a server action. Returned by `redirect()`.
16
+ */
17
+ export declare class RedirectResponse {
18
+ status: number;
19
+ location: string;
20
+ readonly __elur_js_action_redirect = true;
21
+ constructor(status: number, location: string);
22
+ }
23
+ /**
24
+ * Helper to return a validation/error response from a server action.
25
+ *
26
+ * Both argument orders are accepted:
27
+ *
28
+ * ```ts
29
+ * return fail(400, { email: "Invalid email" });
30
+ * return fail({ email: "Invalid email" }, 400);
31
+ * return fail({ email: "Invalid email" }); // defaults to status 400
32
+ * ```
33
+ */
34
+ export declare function fail<TData>(statusOrData: number | TData, dataOrStatus?: TData | number): ActionFailure<unknown>;
35
+ /**
36
+ * Helper to return a redirect from a server action.
37
+ *
38
+ * Both argument orders are accepted:
39
+ *
40
+ * ```ts
41
+ * return redirect(303, "/login");
42
+ * return redirect("/login"); // defaults to status 303
43
+ * ```
44
+ */
45
+ export declare function redirect(statusOrLocation: number | string, locationOrStatus?: string | number): RedirectResponse;
46
+ /**
47
+ * Type guard for action failures. Uses the marker field so it works across
48
+ * bundling boundaries where `instanceof` fails.
49
+ */
50
+ export declare function isActionFailure(value: unknown): value is ActionFailure;
51
+ /**
52
+ * Type guard for redirects. Uses the marker field so it works across bundling
53
+ * boundaries where `instanceof` fails.
54
+ */
55
+ export declare function isRedirectResponse(value: unknown): value is RedirectResponse;
56
+ /**
57
+ * A stable, publicly safe error description. Never includes stacks, internal
58
+ * paths or messages that could leak secrets or filesystem details.
59
+ */
60
+ export interface PublicErrorInfo {
61
+ /** Stable machine-readable code for the response body. */
62
+ code: string;
63
+ /** Stable public message. In non-production this may include the raw message. */
64
+ message: string;
65
+ status: number;
66
+ }
67
+ /**
68
+ * Maps an arbitrary thrown value to a public-safe error info. By default the
69
+ * public message is generic; `includeDetail` (dev/verbose mode) appends the
70
+ * original `Error.message` for local debugging.
71
+ */
72
+ export declare function toPublicErrorInfo(error: unknown, options?: {
73
+ includeDetail?: boolean;
74
+ }): PublicErrorInfo;
75
+ /**
76
+ * Builds a production-safe JSON error Response. Logs the raw error separately
77
+ * (never reflected in the response body) and keeps the request id header.
78
+ */
79
+ export declare function publicErrorResponse(error: unknown, options?: {
80
+ includeDetail?: boolean;
81
+ requestId?: string;
82
+ }): Response;
83
+ /** True when the error should be re-thrown as control flow instead of a 500. */
84
+ export declare function isFirstClassResponse(error: unknown): error is Response;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Represents a failed action result. Returned by `fail()` from server actions.
3
+ *
4
+ * The `__elur_js_action_failure` marker is set on the instance so the server can
5
+ * detect it even when the value crosses a bundling boundary (e.g. the CLI is
6
+ * bundled separately from the user's action modules).
7
+ */
8
+ export declare class ActionFailure<TData = unknown> {
9
+ status: number;
10
+ data: TData;
11
+ readonly __elur_js_action_failure = true;
12
+ constructor(status: number, data: TData);
13
+ }
14
+ /**
15
+ * Represents a redirect returned by a server action. Returned by `redirect()`.
16
+ */
17
+ export declare class RedirectResponse {
18
+ status: number;
19
+ location: string;
20
+ readonly __elur_js_action_redirect = true;
21
+ constructor(status: number, location: string);
22
+ }
23
+ /**
24
+ * Helper to return a validation/error response from a server action.
25
+ *
26
+ * Both argument orders are accepted:
27
+ *
28
+ * ```ts
29
+ * return fail(400, { email: "Invalid email" });
30
+ * return fail({ email: "Invalid email" }, 400);
31
+ * return fail({ email: "Invalid email" }); // defaults to status 400
32
+ * ```
33
+ */
34
+ export declare function fail<TData>(statusOrData: number | TData, dataOrStatus?: TData | number): ActionFailure<unknown>;
35
+ /**
36
+ * Helper to return a redirect from a server action.
37
+ *
38
+ * Both argument orders are accepted:
39
+ *
40
+ * ```ts
41
+ * return redirect(303, "/login");
42
+ * return redirect("/login"); // defaults to status 303
43
+ * ```
44
+ */
45
+ export declare function redirect(statusOrLocation: number | string, locationOrStatus?: string | number): RedirectResponse;
46
+ /**
47
+ * Type guard for action failures. Uses the marker field so it works across
48
+ * bundling boundaries where `instanceof` fails.
49
+ */
50
+ export declare function isActionFailure(value: unknown): value is ActionFailure;
51
+ /**
52
+ * Type guard for redirects. Uses the marker field so it works across bundling
53
+ * boundaries where `instanceof` fails.
54
+ */
55
+ export declare function isRedirectResponse(value: unknown): value is RedirectResponse;
56
+ /**
57
+ * A stable, publicly safe error description. Never includes stacks, internal
58
+ * paths or messages that could leak secrets or filesystem details.
59
+ */
60
+ export interface PublicErrorInfo {
61
+ /** Stable machine-readable code for the response body. */
62
+ code: string;
63
+ /** Stable public message. In non-production this may include the raw message. */
64
+ message: string;
65
+ status: number;
66
+ }
67
+ /**
68
+ * Maps an arbitrary thrown value to a public-safe error info. By default the
69
+ * public message is generic; `includeDetail` (dev/verbose mode) appends the
70
+ * original `Error.message` for local debugging.
71
+ */
72
+ export declare function toPublicErrorInfo(error: unknown, options?: {
73
+ includeDetail?: boolean;
74
+ }): PublicErrorInfo;
75
+ /**
76
+ * Builds a production-safe JSON error Response. Logs the raw error separately
77
+ * (never reflected in the response body) and keeps the request id header.
78
+ */
79
+ export declare function publicErrorResponse(error: unknown, options?: {
80
+ includeDetail?: boolean;
81
+ requestId?: string;
82
+ }): Response;
83
+ /** True when the error should be re-thrown as control flow instead of a 500. */
84
+ export declare function isFirstClassResponse(error: unknown): error is Response;
@@ -0,0 +1,2 @@
1
+ let e=require("@elurjs/core/hydrate");var t=new Set,n=new Set;function r(e){return{load:e}}function i(e){return typeof e==`object`&&!!e&&typeof e.load==`function`}function a(e){return typeof e==`function`?e.constructor?.name===`AsyncFunction`||e[Symbol.toStringTag]===`AsyncFunction`:!1}function o(){return Array.from(document.querySelectorAll(`[data-elur-island]`)).map(e=>{let t={el:e,name:e.dataset.elurJsIsland??``,directive:e.dataset.directive??`load`,props:null};if(e.dataset.props)try{t.props=JSON.parse(e.dataset.props)}catch(e){t.propsError=e}return t})}async function s(n,r){try{if(n.propsError){c(n,n.propsError);return}let o=r[n.name];if(!o){console.warn(`[elur-kit] No island registered for "${n.name}"`);return}let s;if(i(o)){let e=await o.load();s=typeof e==`function`?e:e.default}else if(typeof o==`function`){if(a(o)){let e=await o();s=typeof e==`function`?e:e.default}else s=o}if(typeof s!=`function`){console.warn(`[elur-kit] Island "${n.name}" did not resolve to a component function`);return}let u=s(n.props);if(u===null||u===!1||u===void 0)return;let d=n.el.__elur_js_island_dispose;typeof d==`function`&&d();let f=n.el.innerHTML.includes(`<!--elur-`)?(0,e.hydrate)(u,n.el,{mismatch:`warn-remount`}):l(u,n.el),p=()=>{f.unmount(),t.delete(p),delete n.el.__elur_js_island_dispose};n.el.__elur_js_island_dispose=p,t.add(p)}catch(e){c(n,e)}}function c(e,t){console.error(`[elur-kit] Failed to hydrate island "${e.name}":`,t);let n=e.el.ownerDocument.defaultView?.CustomEvent;n&&e.el.dispatchEvent(new n(`elur:island-error`,{bubbles:!0,detail:{name:e.name,error:t}}))}function l(e,t){return t.replaceChildren(),{unmount:e._render(t,null)}}function u(){for(let e of n)e();n.clear();for(let e of t)e();t.clear()}function d(e){if(typeof window>`u`)return;let t=o();for(let r of t){if(r.directive===`load`||r.directive===`only`){s(r,e);continue}if(r.directive===`idle`){let t=()=>{};if(`requestIdleCallback`in window){let i=window.requestIdleCallback(()=>{n.delete(t),s(r,e)});t=()=>window.cancelIdleCallback(i)}else{let i=globalThis.setTimeout(()=>{n.delete(t),s(r,e)},0);t=()=>globalThis.clearTimeout(i)}n.add(t);continue}if(r.directive===`visible`){if(`IntersectionObserver`in window){let t=()=>{},i=new IntersectionObserver(a=>{for(let o of a)o.isIntersecting&&(n.delete(t),i.disconnect(),s(r,e))},{rootMargin:`0px`,threshold:0});t=()=>i.disconnect(),n.add(t),i.observe(r.el)}else s(r,e)}}}Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return u}});
2
+ //# sourceMappingURL=hydrate-8a3TbQbs.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hydrate-8a3TbQbs.cjs","names":[],"sources":["../../src/island/hydrate.ts"],"sourcesContent":["import type { ElurTemplate } from \"@elurjs/core\";\nimport { hydrate as hydrateTemplate } from \"@elurjs/core/hydrate\";\nimport type { IslandDirective } from \"./island.js\";\n\n// --- Client-side island hydration ---\n\n// Keep track of every active island dispose so we can clean them up before a\n// client-side navigation swaps the whole #app content.\nconst _islandDisposes = new Set<() => void>();\nconst _islandSchedules = new Set<() => void>();\n\n// Finds [data-elur-island] markers in the current document and mounts the\n// corresponding interactive components over them. This runs in the browser.\n\nexport type IslandComponent<TProps = unknown> = (props: TProps) => ElurTemplate | null | false | undefined;\n\n// Re-exported from island.ts so the client entry and the server helper share a\n// single source of truth for the directive union (now includes \"only\").\nexport type { IslandDirective } from \"./island.js\";\n\n/** Lazy island loader in a discriminated form (no probe required to detect). */\nexport interface IslandLoader<TProps = unknown> {\n load: () => Promise<IslandComponent<TProps>>;\n}\n\n/**\n * Island registry entry. Two unambiguous shapes:\n * - `IslandComponent` (eager component function).\n * - `IslandLoader` `{ load }` (lazy, code-split loader).\n *\n * Legacy async loader functions are also accepted for backwards compatibility\n * and detected *without invoking them* (via the `AsyncFunction` tag), so no\n * side effects or duplicate signal creation happen during detection.\n */\nexport type IslandRegistryEntry<TProps = unknown> =\n | IslandComponent<TProps>\n | IslandLoader<TProps>\n | (() => Promise<IslandComponent<TProps>>);\n\nexport type IslandRegistry = Record<string, IslandRegistryEntry<any>>;\n\n/**\n * Wraps a lazy island loader in the discriminated `{ load }` form.\n */\nexport function lazyIsland<TProps = unknown>(\n loader: () => Promise<IslandComponent<TProps>>,\n): IslandLoader<TProps> {\n return { load: loader };\n}\n\nfunction isIslandLoader(value: unknown): value is IslandLoader {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { load?: unknown }).load === \"function\"\n );\n}\n\nfunction isAsyncFunction(value: unknown): boolean {\n if (typeof value !== \"function\") return false;\n const ctor = (value as { constructor?: { name?: string } }).constructor;\n return ctor?.name === \"AsyncFunction\" || (value as { [Symbol.toStringTag]?: string })[Symbol.toStringTag] === \"AsyncFunction\";\n}\n\ninterface IslandMarker {\n el: HTMLElement;\n name: string;\n directive: IslandDirective;\n props: unknown;\n propsError?: unknown;\n}\n\nfunction collectMarkers(): IslandMarker[] {\n const elements = Array.from(\n document.querySelectorAll<HTMLElement>(\"[data-elur-island]\"),\n );\n return elements.map((el) => {\n const marker: IslandMarker = {\n el,\n name: el.dataset.elurJsIsland ?? \"\",\n directive: (el.dataset.directive as IslandDirective) ?? \"load\",\n props: null,\n };\n if (el.dataset.props) {\n try {\n marker.props = JSON.parse(el.dataset.props);\n } catch (error) {\n marker.propsError = error;\n }\n }\n return marker;\n });\n}\n\nasync function hydrate(marker: IslandMarker, registry: IslandRegistry): Promise<void> {\n try {\n if (marker.propsError) {\n reportIslandError(marker, marker.propsError);\n return;\n }\n\n const entry = registry[marker.name];\n if (!entry) {\n console.warn(`[elur-kit] No island registered for \"${marker.name}\"`);\n return;\n }\n\n // Resolve the component without probing: a `{ load }` loader or an async\n // function is lazy (awaited for the module/component); anything else is an\n // eager component called directly with the island props. Detection never\n // invokes the component with undefined props, so no side effects or\n // duplicate signal creation occur during resolution. Eager components\n // hydrate synchronously; lazy loaders hydrate once the module resolves.\n let Component: IslandComponent | undefined;\n if (isIslandLoader(entry)) {\n const mod = (await (entry as IslandLoader).load()) as IslandComponent | { default?: unknown };\n Component = typeof mod === \"function\" ? (mod as IslandComponent) : (mod as { default?: unknown }).default as IslandComponent | undefined;\n } else if (typeof entry === \"function\") {\n if (isAsyncFunction(entry)) {\n const mod = (await (entry as () => Promise<unknown>)()) as IslandComponent | { default?: unknown };\n Component = typeof mod === \"function\" ? (mod as IslandComponent) : (mod as { default?: unknown }).default as IslandComponent | undefined;\n } else {\n Component = entry as IslandComponent;\n }\n }\n\n if (typeof Component !== \"function\") {\n console.warn(`[elur-kit] Island \"${marker.name}\" did not resolve to a component function`);\n return;\n }\n\n const template = Component(marker.props);\n if (template === null || template === false || template === undefined) return;\n const prevDispose = (marker.el as any).__elur_js_island_dispose;\n if (typeof prevDispose === \"function\") prevDispose();\n\n // Islands with directive \"only\" or options.ssr:false have no SSR-rendered\n // DOM inside the marker — only fallback HTML or nothing. hydrateTemplate\n // assumes the SSR DOM is already present and walks it for hydration markers\n // (<!--elur-N-->, data-elur-e-*). When there's nothing to walk, it silently\n // does nothing (no contexts to match → empty loop → no mount). So we detect\n // the absence of hydration markers and do a fresh _render mount instead.\n const hasSSRMarkers = marker.el.innerHTML.includes(\"<!--elur-\");\n const handle = hasSSRMarkers\n ? hydrateTemplate(template, marker.el, { mismatch: \"warn-remount\" })\n : freshMount(template, marker.el);\n\n const wrappedDispose = () => {\n handle.unmount();\n _islandDisposes.delete(wrappedDispose);\n delete (marker.el as any).__elur_js_island_dispose;\n };\n (marker.el as any).__elur_js_island_dispose = wrappedDispose;\n _islandDisposes.add(wrappedDispose);\n } catch (error) {\n reportIslandError(marker, error);\n }\n}\n\nfunction reportIslandError(marker: IslandMarker, error: unknown): void {\n console.error(`[elur-kit] Failed to hydrate island \"${marker.name}\":`, error);\n const EventConstructor = marker.el.ownerDocument.defaultView?.CustomEvent;\n if (EventConstructor) {\n marker.el.dispatchEvent(new EventConstructor(\"elur:island-error\", {\n bubbles: true,\n detail: { name: marker.name, error },\n }));\n }\n}\n\n/**\n * Mounts a template fresh into a container (no hydration).\n * Used for islands with no SSR DOM (directive \"only\" or ssr:false) where\n * hydrateTemplate can't work — there's nothing to hydrate against.\n */\nfunction freshMount(template: ElurTemplate, container: Element): { unmount: () => void } {\n container.replaceChildren();\n const dispose = template._render(container, null);\n return { unmount: dispose };\n}\n\n/**\n * Hydrates all islands on the page using the provided registry.\n *\n * @param registry Map from island name to component factory.\n */\n/**\n * Dispose all currently hydrated islands. Called by the client router before\n * swapping the page body to prevent leaked effects and stale DOM writes.\n */\nexport function cleanupHydratedIslands(): void {\n for (const cancel of _islandSchedules) cancel();\n _islandSchedules.clear();\n for (const dispose of _islandDisposes) dispose();\n _islandDisposes.clear();\n}\n\nexport function hydrateIslands(registry: IslandRegistry): void {\n if (typeof window === \"undefined\") return;\n\n const markers = collectMarkers();\n\n for (const marker of markers) {\n if (marker.directive === \"load\" || marker.directive === \"only\") {\n // \"only\" is client-only (no SSR) but hydrates immediately on the\n // client, just like \"load\" — the difference is purely server-side.\n void hydrate(marker, registry);\n continue;\n }\n\n if (marker.directive === \"idle\") {\n let cancel = () => { };\n if (\"requestIdleCallback\" in window) {\n const id = window.requestIdleCallback(() => {\n _islandSchedules.delete(cancel);\n void hydrate(marker, registry);\n });\n cancel = () => window.cancelIdleCallback(id);\n } else {\n const id = globalThis.setTimeout(() => {\n _islandSchedules.delete(cancel);\n void hydrate(marker, registry);\n }, 0);\n cancel = () => globalThis.clearTimeout(id);\n }\n _islandSchedules.add(cancel);\n continue;\n }\n\n if (marker.directive === \"visible\") {\n if (\"IntersectionObserver\" in window) {\n let cancel = () => { };\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n _islandSchedules.delete(cancel);\n observer.disconnect();\n void hydrate(marker, registry);\n }\n }\n },\n { rootMargin: \"0px\", threshold: 0 },\n );\n cancel = () => observer.disconnect();\n _islandSchedules.add(cancel);\n observer.observe(marker.el);\n } else {\n void hydrate(marker, registry);\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,126 @@
1
+ import { hydrate as e } from "@elurjs/core/hydrate";
2
+ //#region src/island/hydrate.ts
3
+ var t = /* @__PURE__ */ new Set(), n = /* @__PURE__ */ new Set();
4
+ function r(e) {
5
+ return { load: e };
6
+ }
7
+ function i(e) {
8
+ return typeof e == "object" && !!e && typeof e.load == "function";
9
+ }
10
+ function a(e) {
11
+ return typeof e == "function" ? e.constructor?.name === "AsyncFunction" || e[Symbol.toStringTag] === "AsyncFunction" : !1;
12
+ }
13
+ function o() {
14
+ return Array.from(document.querySelectorAll("[data-elur-island]")).map((e) => {
15
+ let t = {
16
+ el: e,
17
+ name: e.dataset.elurJsIsland ?? "",
18
+ directive: e.dataset.directive ?? "load",
19
+ props: null
20
+ };
21
+ if (e.dataset.props) try {
22
+ t.props = JSON.parse(e.dataset.props);
23
+ } catch (e) {
24
+ t.propsError = e;
25
+ }
26
+ return t;
27
+ });
28
+ }
29
+ async function s(n, r) {
30
+ try {
31
+ if (n.propsError) {
32
+ c(n, n.propsError);
33
+ return;
34
+ }
35
+ let o = r[n.name];
36
+ if (!o) {
37
+ console.warn(`[elur-kit] No island registered for "${n.name}"`);
38
+ return;
39
+ }
40
+ let s;
41
+ if (i(o)) {
42
+ let e = await o.load();
43
+ s = typeof e == "function" ? e : e.default;
44
+ } else if (typeof o == "function") {
45
+ if (a(o)) {
46
+ let e = await o();
47
+ s = typeof e == "function" ? e : e.default;
48
+ } else s = o;
49
+ }
50
+ if (typeof s != "function") {
51
+ console.warn(`[elur-kit] Island "${n.name}" did not resolve to a component function`);
52
+ return;
53
+ }
54
+ let u = s(n.props);
55
+ if (u === null || u === !1 || u === void 0) return;
56
+ let d = n.el.__elur_js_island_dispose;
57
+ typeof d == "function" && d();
58
+ let f = n.el.innerHTML.includes("<!--elur-") ? e(u, n.el, { mismatch: "warn-remount" }) : l(u, n.el), p = () => {
59
+ f.unmount(), t.delete(p), delete n.el.__elur_js_island_dispose;
60
+ };
61
+ n.el.__elur_js_island_dispose = p, t.add(p);
62
+ } catch (e) {
63
+ c(n, e);
64
+ }
65
+ }
66
+ function c(e, t) {
67
+ console.error(`[elur-kit] Failed to hydrate island "${e.name}":`, t);
68
+ let n = e.el.ownerDocument.defaultView?.CustomEvent;
69
+ n && e.el.dispatchEvent(new n("elur:island-error", {
70
+ bubbles: !0,
71
+ detail: {
72
+ name: e.name,
73
+ error: t
74
+ }
75
+ }));
76
+ }
77
+ function l(e, t) {
78
+ return t.replaceChildren(), { unmount: e._render(t, null) };
79
+ }
80
+ function u() {
81
+ for (let e of n) e();
82
+ n.clear();
83
+ for (let e of t) e();
84
+ t.clear();
85
+ }
86
+ function d(e) {
87
+ if (typeof window > "u") return;
88
+ let t = o();
89
+ for (let r of t) {
90
+ if (r.directive === "load" || r.directive === "only") {
91
+ s(r, e);
92
+ continue;
93
+ }
94
+ if (r.directive === "idle") {
95
+ let t = () => {};
96
+ if ("requestIdleCallback" in window) {
97
+ let i = window.requestIdleCallback(() => {
98
+ n.delete(t), s(r, e);
99
+ });
100
+ t = () => window.cancelIdleCallback(i);
101
+ } else {
102
+ let i = globalThis.setTimeout(() => {
103
+ n.delete(t), s(r, e);
104
+ }, 0);
105
+ t = () => globalThis.clearTimeout(i);
106
+ }
107
+ n.add(t);
108
+ continue;
109
+ }
110
+ if (r.directive === "visible") {
111
+ if ("IntersectionObserver" in window) {
112
+ let t = () => {}, i = new IntersectionObserver((a) => {
113
+ for (let o of a) o.isIntersecting && (n.delete(t), i.disconnect(), s(r, e));
114
+ }, {
115
+ rootMargin: "0px",
116
+ threshold: 0
117
+ });
118
+ t = () => i.disconnect(), n.add(t), i.observe(r.el);
119
+ } else s(r, e);
120
+ }
121
+ }
122
+ }
123
+ //#endregion
124
+ export { d as n, r, u as t };
125
+
126
+ //# sourceMappingURL=hydrate-CgZGrTxF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hydrate-CgZGrTxF.js","names":[],"sources":["../../src/island/hydrate.ts"],"sourcesContent":["import type { ElurTemplate } from \"@elurjs/core\";\nimport { hydrate as hydrateTemplate } from \"@elurjs/core/hydrate\";\nimport type { IslandDirective } from \"./island.js\";\n\n// --- Client-side island hydration ---\n\n// Keep track of every active island dispose so we can clean them up before a\n// client-side navigation swaps the whole #app content.\nconst _islandDisposes = new Set<() => void>();\nconst _islandSchedules = new Set<() => void>();\n\n// Finds [data-elur-island] markers in the current document and mounts the\n// corresponding interactive components over them. This runs in the browser.\n\nexport type IslandComponent<TProps = unknown> = (props: TProps) => ElurTemplate | null | false | undefined;\n\n// Re-exported from island.ts so the client entry and the server helper share a\n// single source of truth for the directive union (now includes \"only\").\nexport type { IslandDirective } from \"./island.js\";\n\n/** Lazy island loader in a discriminated form (no probe required to detect). */\nexport interface IslandLoader<TProps = unknown> {\n load: () => Promise<IslandComponent<TProps>>;\n}\n\n/**\n * Island registry entry. Two unambiguous shapes:\n * - `IslandComponent` (eager component function).\n * - `IslandLoader` `{ load }` (lazy, code-split loader).\n *\n * Legacy async loader functions are also accepted for backwards compatibility\n * and detected *without invoking them* (via the `AsyncFunction` tag), so no\n * side effects or duplicate signal creation happen during detection.\n */\nexport type IslandRegistryEntry<TProps = unknown> =\n | IslandComponent<TProps>\n | IslandLoader<TProps>\n | (() => Promise<IslandComponent<TProps>>);\n\nexport type IslandRegistry = Record<string, IslandRegistryEntry<any>>;\n\n/**\n * Wraps a lazy island loader in the discriminated `{ load }` form.\n */\nexport function lazyIsland<TProps = unknown>(\n loader: () => Promise<IslandComponent<TProps>>,\n): IslandLoader<TProps> {\n return { load: loader };\n}\n\nfunction isIslandLoader(value: unknown): value is IslandLoader {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { load?: unknown }).load === \"function\"\n );\n}\n\nfunction isAsyncFunction(value: unknown): boolean {\n if (typeof value !== \"function\") return false;\n const ctor = (value as { constructor?: { name?: string } }).constructor;\n return ctor?.name === \"AsyncFunction\" || (value as { [Symbol.toStringTag]?: string })[Symbol.toStringTag] === \"AsyncFunction\";\n}\n\ninterface IslandMarker {\n el: HTMLElement;\n name: string;\n directive: IslandDirective;\n props: unknown;\n propsError?: unknown;\n}\n\nfunction collectMarkers(): IslandMarker[] {\n const elements = Array.from(\n document.querySelectorAll<HTMLElement>(\"[data-elur-island]\"),\n );\n return elements.map((el) => {\n const marker: IslandMarker = {\n el,\n name: el.dataset.elurJsIsland ?? \"\",\n directive: (el.dataset.directive as IslandDirective) ?? \"load\",\n props: null,\n };\n if (el.dataset.props) {\n try {\n marker.props = JSON.parse(el.dataset.props);\n } catch (error) {\n marker.propsError = error;\n }\n }\n return marker;\n });\n}\n\nasync function hydrate(marker: IslandMarker, registry: IslandRegistry): Promise<void> {\n try {\n if (marker.propsError) {\n reportIslandError(marker, marker.propsError);\n return;\n }\n\n const entry = registry[marker.name];\n if (!entry) {\n console.warn(`[elur-kit] No island registered for \"${marker.name}\"`);\n return;\n }\n\n // Resolve the component without probing: a `{ load }` loader or an async\n // function is lazy (awaited for the module/component); anything else is an\n // eager component called directly with the island props. Detection never\n // invokes the component with undefined props, so no side effects or\n // duplicate signal creation occur during resolution. Eager components\n // hydrate synchronously; lazy loaders hydrate once the module resolves.\n let Component: IslandComponent | undefined;\n if (isIslandLoader(entry)) {\n const mod = (await (entry as IslandLoader).load()) as IslandComponent | { default?: unknown };\n Component = typeof mod === \"function\" ? (mod as IslandComponent) : (mod as { default?: unknown }).default as IslandComponent | undefined;\n } else if (typeof entry === \"function\") {\n if (isAsyncFunction(entry)) {\n const mod = (await (entry as () => Promise<unknown>)()) as IslandComponent | { default?: unknown };\n Component = typeof mod === \"function\" ? (mod as IslandComponent) : (mod as { default?: unknown }).default as IslandComponent | undefined;\n } else {\n Component = entry as IslandComponent;\n }\n }\n\n if (typeof Component !== \"function\") {\n console.warn(`[elur-kit] Island \"${marker.name}\" did not resolve to a component function`);\n return;\n }\n\n const template = Component(marker.props);\n if (template === null || template === false || template === undefined) return;\n const prevDispose = (marker.el as any).__elur_js_island_dispose;\n if (typeof prevDispose === \"function\") prevDispose();\n\n // Islands with directive \"only\" or options.ssr:false have no SSR-rendered\n // DOM inside the marker — only fallback HTML or nothing. hydrateTemplate\n // assumes the SSR DOM is already present and walks it for hydration markers\n // (<!--elur-N-->, data-elur-e-*). When there's nothing to walk, it silently\n // does nothing (no contexts to match → empty loop → no mount). So we detect\n // the absence of hydration markers and do a fresh _render mount instead.\n const hasSSRMarkers = marker.el.innerHTML.includes(\"<!--elur-\");\n const handle = hasSSRMarkers\n ? hydrateTemplate(template, marker.el, { mismatch: \"warn-remount\" })\n : freshMount(template, marker.el);\n\n const wrappedDispose = () => {\n handle.unmount();\n _islandDisposes.delete(wrappedDispose);\n delete (marker.el as any).__elur_js_island_dispose;\n };\n (marker.el as any).__elur_js_island_dispose = wrappedDispose;\n _islandDisposes.add(wrappedDispose);\n } catch (error) {\n reportIslandError(marker, error);\n }\n}\n\nfunction reportIslandError(marker: IslandMarker, error: unknown): void {\n console.error(`[elur-kit] Failed to hydrate island \"${marker.name}\":`, error);\n const EventConstructor = marker.el.ownerDocument.defaultView?.CustomEvent;\n if (EventConstructor) {\n marker.el.dispatchEvent(new EventConstructor(\"elur:island-error\", {\n bubbles: true,\n detail: { name: marker.name, error },\n }));\n }\n}\n\n/**\n * Mounts a template fresh into a container (no hydration).\n * Used for islands with no SSR DOM (directive \"only\" or ssr:false) where\n * hydrateTemplate can't work — there's nothing to hydrate against.\n */\nfunction freshMount(template: ElurTemplate, container: Element): { unmount: () => void } {\n container.replaceChildren();\n const dispose = template._render(container, null);\n return { unmount: dispose };\n}\n\n/**\n * Hydrates all islands on the page using the provided registry.\n *\n * @param registry Map from island name to component factory.\n */\n/**\n * Dispose all currently hydrated islands. Called by the client router before\n * swapping the page body to prevent leaked effects and stale DOM writes.\n */\nexport function cleanupHydratedIslands(): void {\n for (const cancel of _islandSchedules) cancel();\n _islandSchedules.clear();\n for (const dispose of _islandDisposes) dispose();\n _islandDisposes.clear();\n}\n\nexport function hydrateIslands(registry: IslandRegistry): void {\n if (typeof window === \"undefined\") return;\n\n const markers = collectMarkers();\n\n for (const marker of markers) {\n if (marker.directive === \"load\" || marker.directive === \"only\") {\n // \"only\" is client-only (no SSR) but hydrates immediately on the\n // client, just like \"load\" — the difference is purely server-side.\n void hydrate(marker, registry);\n continue;\n }\n\n if (marker.directive === \"idle\") {\n let cancel = () => { };\n if (\"requestIdleCallback\" in window) {\n const id = window.requestIdleCallback(() => {\n _islandSchedules.delete(cancel);\n void hydrate(marker, registry);\n });\n cancel = () => window.cancelIdleCallback(id);\n } else {\n const id = globalThis.setTimeout(() => {\n _islandSchedules.delete(cancel);\n void hydrate(marker, registry);\n }, 0);\n cancel = () => globalThis.clearTimeout(id);\n }\n _islandSchedules.add(cancel);\n continue;\n }\n\n if (marker.directive === \"visible\") {\n if (\"IntersectionObserver\" in window) {\n let cancel = () => { };\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n _islandSchedules.delete(cancel);\n observer.disconnect();\n void hydrate(marker, registry);\n }\n }\n },\n { rootMargin: \"0px\", threshold: 0 },\n );\n cancel = () => observer.disconnect();\n _islandSchedules.add(cancel);\n observer.observe(marker.el);\n } else {\n void hydrate(marker, registry);\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./service.cjs"),t=require("./registry.cjs");let n=require("@elurjs/core");function r(r){let{src:a,alt:o,width:s,height:c,widths:l=[s],sizes:u,priority:d=!1,class:f,attributes:p={}}=r;t.registerImage(a,l);let m,h=t.getActiveManifest(),g=h?e.getManifestEntry(h,a):void 0;if(g&&g.variants.length>0)m=e.buildPictureMarkup(g,{alt:o,sizes:u,priority:d,class:f,attributes:p,fallbackSrc:a,fallbackWidth:s,fallbackHeight:c});else{let e=d?``:` loading="lazy"`,t=d?` fetchpriority="high"`:``,n=u?` sizes="${i(u)}"`:``,r=f?` class="${i(f)}"`:``,l=Object.entries(p).map(([e,t])=>` ${i(e)}="${i(String(t))}"`).join(``);m=`<img src="${i(a)}" alt="${i(o)}" width="${s}" height="${c}"${n}${e} decoding="async"${t}${r}${l} />`}return{__isElurTemplate:!0,[n.ELUR_RENDER_PROTOCOL]:{renderServer:()=>m},mount(e){let t=typeof e==`string`?document.querySelector(e):e;if(!t)throw Error(`[elur-kit] image(): container not found`);return t.innerHTML=m,{unmount(){t.innerHTML=``}}},_render(e,t){let n=document.createElement(`template`);n.innerHTML=m;let r=n.content,i=Array.from(r.childNodes);return e.insertBefore(r,t),()=>{for(let e of i)e.parentNode&&e.parentNode.removeChild(e)}}}}function i(e){return e.replace(/&/g,`&amp;`).replace(/"/g,`&quot;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`)}exports.buildPictureMarkup=e.buildPictureMarkup,exports.buildSrcset=e.buildSrcset,exports.consumeImageRegistry=t.consumeImageRegistry,exports.createImageService=e.createImageService,exports.getImage=e.getImage,exports.getManifestEntry=e.getManifestEntry,exports.image=r,exports.isSharpAvailable=e.isSharpAvailable,exports.processImageBatch=e.processImageBatch,exports.readManifest=e.readManifest,exports.setImageManifest=t.setImageManifest,exports.transformHash=e.transformHash,exports.validateManifestUrls=e.validateManifestUrls,exports.writeManifest=e.writeManifest;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../../src/image/index.ts"],"sourcesContent":["// --- image() helper: optimized <img> / <picture> without dependencies ---\n//\n// Emits an `<img>` (or `<picture>` when processed variants are available) with:\n// - `srcset` / `sizes` for responsive images\n// - `loading=\"lazy\"` and `decoding=\"async\"` (unless `priority`)\n// - `width` / `height` to prevent layout shift (CLS)\n// - `fetchpriority=\"high\"` for above-the-fold images\n//\n// When the build-time pipeline (sharp) is available, `image()` registers the\n// source for processing and emits `<picture>` with `<source>` per format.\n// When sharp is not installed, it falls back to a plain `<img>` with srcset\n// pointing to the original file.\n\nimport { ELUR_RENDER_PROTOCOL, type ElurTemplate } from \"@elurjs/core\";\nimport { getManifestEntry, buildPictureMarkup } from \"./service.js\";\nimport {\n registerImage,\n getActiveManifest,\n} from \"./registry.js\";\n\nexport { consumeImageRegistry, setImageManifest, type ImageFormat } from \"./registry.js\";\n\nexport interface ImageOptions {\n /** Source path relative to the public directory, e.g. \"/images/hero.jpg\". */\n src: string;\n /** Alt text (required for accessibility). */\n alt: string;\n /** Intrinsic width of the source image in pixels. */\n width: number;\n /** Intrinsic height of the source image in pixels. */\n height: number;\n /** Responsive widths to generate for srcset. Defaults to [width]. */\n widths?: number[];\n /** Sizes attribute for responsive layout, e.g. \"100vw\" or \"(min-width: 768px) 50vw, 100vw\". */\n sizes?: string;\n /** If true, loads eagerly with high priority (for above-the-fold images). */\n priority?: boolean;\n /** CSS class for the img element. */\n class?: string;\n /** Additional HTML attributes. */\n attributes?: Record<string, string>;\n}\n\n/**\n * Creates a ElurTemplate that renders an optimized `<img>` or `<picture>` element.\n *\n * When a manifest is active (post-build or SSR with manifest loaded):\n * * Emits `<picture>` with `<source>` per format and real hashed variant URLs.\n * * Uses real dimensions from the manifest.\n *\n * When no manifest is active (first build pass, dev without sharp):\n * * Emits a plain `<img>` with the original src.\n * * Does NOT emit srcset with unresolved variant URLs.\n * * Registers the image for build-time processing.\n */\nexport function image(opts: ImageOptions): ElurTemplate {\n const {\n src,\n alt,\n width,\n height,\n widths = [width],\n sizes,\n priority = false,\n class: className,\n attributes = {},\n } = opts;\n\n // Register for build-time processing.\n registerImage(src, widths);\n\n // If a manifest is active and has an entry for this src, emit <picture>.\n let html: string;\n const manifest = getActiveManifest();\n const entry = manifest ? getManifestEntry(manifest, src) : undefined;\n if (entry && entry.variants.length > 0) {\n html = buildPictureMarkup(entry, {\n alt,\n sizes,\n priority,\n class: className,\n attributes,\n fallbackSrc: src,\n fallbackWidth: width,\n fallbackHeight: height,\n });\n } else {\n // No manifest or no variants: emit a plain <img> with the original src.\n // Do NOT emit srcset — the variant files don't exist yet.\n const loadingAttr = priority ? \"\" : ' loading=\"lazy\"';\n const fetchPriorityAttr = priority ? ' fetchpriority=\"high\"' : \"\";\n const sizesAttr = sizes ? ` sizes=\"${escapeAttr(sizes)}\"` : \"\";\n const classAttr = className ? ` class=\"${escapeAttr(className)}\"` : \"\";\n const extraAttrs = Object.entries(attributes)\n .map(([key, value]) => ` ${escapeAttr(key)}=\"${escapeAttr(String(value))}\"`)\n .join(\"\");\n html = `<img src=\"${escapeAttr(src)}\" alt=\"${escapeAttr(alt)}\" width=\"${width}\" height=\"${height}\"${sizesAttr}${loadingAttr} decoding=\"async\"${fetchPriorityAttr}${classAttr}${extraAttrs} />`;\n }\n\n return {\n __isElurTemplate: true as const,\n [ELUR_RENDER_PROTOCOL]: {\n renderServer: () => html,\n },\n mount(container: Element | string) {\n const el = typeof container === \"string\" ? document.querySelector(container) : container;\n if (!el) throw new Error(\"[elur-kit] image(): container not found\");\n el.innerHTML = html;\n return { unmount() { el.innerHTML = \"\"; } };\n },\n _render(parent: Node, before: Node | null): () => void {\n const wrapper = document.createElement(\"template\");\n wrapper.innerHTML = html;\n const fragment = wrapper.content;\n const inserted = Array.from(fragment.childNodes);\n parent.insertBefore(fragment, before);\n return () => {\n for (const node of inserted) {\n if (node.parentNode) node.parentNode.removeChild(node);\n }\n };\n },\n } as unknown as ElurTemplate;\n}\n\nfunction escapeAttr(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n\nexport {\n processImageBatch,\n readManifest,\n writeManifest,\n getManifestEntry,\n buildSrcset,\n buildPictureMarkup,\n validateManifestUrls,\n isSharpAvailable,\n getImage,\n createImageService,\n transformHash,\n} from \"./service.js\";\nexport type {\n ImageManifest,\n ImageEntry,\n ImageVariant,\n ProcessOptions,\n ProcessResult,\n ImageRequest,\n ImageMetadata,\n GeneratedImage,\n ImageService,\n ImageServiceCapabilities,\n ImageServiceContext,\n} from \"./service.js\";\n"],"mappings":"8JAuDA,SAAgB,EAAM,EAAkC,CACtD,GAAM,CACJ,MACA,MACA,QACA,SACA,SAAS,CAAC,CAAK,EACf,QACA,WAAW,GACX,MAAO,EACP,aAAa,CAAC,GACZ,EAGJ,EAAA,cAAc,EAAK,CAAM,EAGzB,IAAI,EACE,EAAW,EAAA,kBAAkB,EAC7B,EAAQ,EAAW,EAAA,iBAAiB,EAAU,CAAG,EAAI,IAAA,GAC3D,GAAI,GAAS,EAAM,SAAS,OAAS,EACnC,EAAO,EAAA,mBAAmB,EAAO,CAC/B,MACA,QACA,WACA,MAAO,EACP,aACA,YAAa,EACb,cAAe,EACf,eAAgB,CAClB,CAAC,MACI,CAGL,IAAM,EAAc,EAAW,GAAK,kBAC9B,EAAoB,EAAW,wBAA0B,GACzD,EAAY,EAAQ,WAAW,EAAW,CAAK,EAAE,GAAK,GACtD,EAAY,EAAY,WAAW,EAAW,CAAS,EAAE,GAAK,GAC9D,EAAa,OAAO,QAAQ,CAAU,CAAC,CAC1C,KAAK,CAAC,EAAK,KAAW,IAAI,EAAW,CAAG,EAAE,IAAI,EAAW,OAAO,CAAK,CAAC,EAAE,EAAE,CAAC,CAC3E,KAAK,EAAE,EACV,EAAO,aAAa,EAAW,CAAG,EAAE,SAAS,EAAW,CAAG,EAAE,WAAW,EAAM,YAAY,EAAO,GAAG,IAAY,EAAY,mBAAmB,IAAoB,IAAY,EAAW,IAC5L,CAEA,MAAO,CACL,iBAAkB,IACjB,EAAA,sBAAuB,CACtB,iBAAoB,CACtB,EACA,MAAM,EAA6B,CACjC,IAAM,EAAK,OAAO,GAAc,SAAW,SAAS,cAAc,CAAS,EAAI,EAC/E,GAAI,CAAC,EAAI,MAAU,MAAM,yCAAyC,EAElE,MADA,GAAG,UAAY,EACR,CAAE,SAAU,CAAE,EAAG,UAAY,EAAI,CAAE,CAC5C,EACA,QAAQ,EAAc,EAAiC,CACrD,IAAM,EAAU,SAAS,cAAc,UAAU,EACjD,EAAQ,UAAY,EACpB,IAAM,EAAW,EAAQ,QACnB,EAAW,MAAM,KAAK,EAAS,UAAU,EAE/C,OADA,EAAO,aAAa,EAAU,CAAM,MACvB,CACX,IAAK,IAAM,KAAQ,EACb,EAAK,YAAY,EAAK,WAAW,YAAY,CAAI,CAEzD,CACF,CACF,CACF,CAEA,SAAS,EAAW,EAAuB,CACzC,OAAO,EACJ,QAAQ,KAAM,OAAO,CAAC,CACtB,QAAQ,KAAM,QAAQ,CAAC,CACvB,QAAQ,KAAM,MAAM,CAAC,CACrB,QAAQ,KAAM,MAAM,CACzB"}
@@ -0,0 +1,37 @@
1
+ import { type ElurTemplate } from "@elurjs/core";
2
+ export { consumeImageRegistry, setImageManifest, type ImageFormat } from "./registry.js";
3
+ export interface ImageOptions {
4
+ /** Source path relative to the public directory, e.g. "/images/hero.jpg". */
5
+ src: string;
6
+ /** Alt text (required for accessibility). */
7
+ alt: string;
8
+ /** Intrinsic width of the source image in pixels. */
9
+ width: number;
10
+ /** Intrinsic height of the source image in pixels. */
11
+ height: number;
12
+ /** Responsive widths to generate for srcset. Defaults to [width]. */
13
+ widths?: number[];
14
+ /** Sizes attribute for responsive layout, e.g. "100vw" or "(min-width: 768px) 50vw, 100vw". */
15
+ sizes?: string;
16
+ /** If true, loads eagerly with high priority (for above-the-fold images). */
17
+ priority?: boolean;
18
+ /** CSS class for the img element. */
19
+ class?: string;
20
+ /** Additional HTML attributes. */
21
+ attributes?: Record<string, string>;
22
+ }
23
+ /**
24
+ * Creates a ElurTemplate that renders an optimized `<img>` or `<picture>` element.
25
+ *
26
+ * When a manifest is active (post-build or SSR with manifest loaded):
27
+ * * Emits `<picture>` with `<source>` per format and real hashed variant URLs.
28
+ * * Uses real dimensions from the manifest.
29
+ *
30
+ * When no manifest is active (first build pass, dev without sharp):
31
+ * * Emits a plain `<img>` with the original src.
32
+ * * Does NOT emit srcset with unresolved variant URLs.
33
+ * * Registers the image for build-time processing.
34
+ */
35
+ export declare function image(opts: ImageOptions): ElurTemplate;
36
+ export { processImageBatch, readManifest, writeManifest, getManifestEntry, buildSrcset, buildPictureMarkup, validateManifestUrls, isSharpAvailable, getImage, createImageService, transformHash, } from "./service.js";
37
+ export type { ImageManifest, ImageEntry, ImageVariant, ProcessOptions, ProcessResult, ImageRequest, ImageMetadata, GeneratedImage, ImageService, ImageServiceCapabilities, ImageServiceContext, } from "./service.js";
@@ -0,0 +1,37 @@
1
+ import { type ElurTemplate } from "@elurjs/core";
2
+ export { consumeImageRegistry, setImageManifest, type ImageFormat } from "./registry.js";
3
+ export interface ImageOptions {
4
+ /** Source path relative to the public directory, e.g. "/images/hero.jpg". */
5
+ src: string;
6
+ /** Alt text (required for accessibility). */
7
+ alt: string;
8
+ /** Intrinsic width of the source image in pixels. */
9
+ width: number;
10
+ /** Intrinsic height of the source image in pixels. */
11
+ height: number;
12
+ /** Responsive widths to generate for srcset. Defaults to [width]. */
13
+ widths?: number[];
14
+ /** Sizes attribute for responsive layout, e.g. "100vw" or "(min-width: 768px) 50vw, 100vw". */
15
+ sizes?: string;
16
+ /** If true, loads eagerly with high priority (for above-the-fold images). */
17
+ priority?: boolean;
18
+ /** CSS class for the img element. */
19
+ class?: string;
20
+ /** Additional HTML attributes. */
21
+ attributes?: Record<string, string>;
22
+ }
23
+ /**
24
+ * Creates a ElurTemplate that renders an optimized `<img>` or `<picture>` element.
25
+ *
26
+ * When a manifest is active (post-build or SSR with manifest loaded):
27
+ * * Emits `<picture>` with `<source>` per format and real hashed variant URLs.
28
+ * * Uses real dimensions from the manifest.
29
+ *
30
+ * When no manifest is active (first build pass, dev without sharp):
31
+ * * Emits a plain `<img>` with the original src.
32
+ * * Does NOT emit srcset with unresolved variant URLs.
33
+ * * Registers the image for build-time processing.
34
+ */
35
+ export declare function image(opts: ImageOptions): ElurTemplate;
36
+ export { processImageBatch, readManifest, writeManifest, getManifestEntry, buildSrcset, buildPictureMarkup, validateManifestUrls, isSharpAvailable, getImage, createImageService, transformHash, } from "./service.js";
37
+ export type { ImageManifest, ImageEntry, ImageVariant, ProcessOptions, ProcessResult, ImageRequest, ImageMetadata, GeneratedImage, ImageService, ImageServiceCapabilities, ImageServiceContext, } from "./service.js";
@@ -0,0 +1,49 @@
1
+ import { buildPictureMarkup as e, buildSrcset as t, createImageService as n, getImage as r, getManifestEntry as i, isSharpAvailable as a, processImageBatch as o, readManifest as s, transformHash as c, validateManifestUrls as l, writeManifest as u } from "./service.js";
2
+ import { consumeImageRegistry as d, getActiveManifest as f, registerImage as p, setImageManifest as m } from "./registry.js";
3
+ import { ELUR_RENDER_PROTOCOL as h } from "@elurjs/core";
4
+ //#region src/image/index.ts
5
+ function g(t) {
6
+ let { src: n, alt: r, width: a, height: o, widths: s = [a], sizes: c, priority: l = !1, class: u, attributes: d = {} } = t;
7
+ p(n, s);
8
+ let m, g = f(), v = g ? i(g, n) : void 0;
9
+ if (v && v.variants.length > 0) m = e(v, {
10
+ alt: r,
11
+ sizes: c,
12
+ priority: l,
13
+ class: u,
14
+ attributes: d,
15
+ fallbackSrc: n,
16
+ fallbackWidth: a,
17
+ fallbackHeight: o
18
+ });
19
+ else {
20
+ let e = l ? "" : " loading=\"lazy\"", t = l ? " fetchpriority=\"high\"" : "", i = c ? ` sizes="${_(c)}"` : "", s = u ? ` class="${_(u)}"` : "", f = Object.entries(d).map(([e, t]) => ` ${_(e)}="${_(String(t))}"`).join("");
21
+ m = `<img src="${_(n)}" alt="${_(r)}" width="${a}" height="${o}"${i}${e} decoding="async"${t}${s}${f} />`;
22
+ }
23
+ return {
24
+ __isElurTemplate: !0,
25
+ [h]: { renderServer: () => m },
26
+ mount(e) {
27
+ let t = typeof e == "string" ? document.querySelector(e) : e;
28
+ if (!t) throw Error("[elur-kit] image(): container not found");
29
+ return t.innerHTML = m, { unmount() {
30
+ t.innerHTML = "";
31
+ } };
32
+ },
33
+ _render(e, t) {
34
+ let n = document.createElement("template");
35
+ n.innerHTML = m;
36
+ let r = n.content, i = Array.from(r.childNodes);
37
+ return e.insertBefore(r, t), () => {
38
+ for (let e of i) e.parentNode && e.parentNode.removeChild(e);
39
+ };
40
+ }
41
+ };
42
+ }
43
+ function _(e) {
44
+ return e.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
45
+ }
46
+ //#endregion
47
+ export { e as buildPictureMarkup, t as buildSrcset, d as consumeImageRegistry, n as createImageService, r as getImage, i as getManifestEntry, g as image, a as isSharpAvailable, o as processImageBatch, s as readManifest, m as setImageManifest, c as transformHash, l as validateManifestUrls, u as writeManifest };
48
+
49
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/image/index.ts"],"sourcesContent":["// --- image() helper: optimized <img> / <picture> without dependencies ---\n//\n// Emits an `<img>` (or `<picture>` when processed variants are available) with:\n// - `srcset` / `sizes` for responsive images\n// - `loading=\"lazy\"` and `decoding=\"async\"` (unless `priority`)\n// - `width` / `height` to prevent layout shift (CLS)\n// - `fetchpriority=\"high\"` for above-the-fold images\n//\n// When the build-time pipeline (sharp) is available, `image()` registers the\n// source for processing and emits `<picture>` with `<source>` per format.\n// When sharp is not installed, it falls back to a plain `<img>` with srcset\n// pointing to the original file.\n\nimport { ELUR_RENDER_PROTOCOL, type ElurTemplate } from \"@elurjs/core\";\nimport { getManifestEntry, buildPictureMarkup } from \"./service.js\";\nimport {\n registerImage,\n getActiveManifest,\n} from \"./registry.js\";\n\nexport { consumeImageRegistry, setImageManifest, type ImageFormat } from \"./registry.js\";\n\nexport interface ImageOptions {\n /** Source path relative to the public directory, e.g. \"/images/hero.jpg\". */\n src: string;\n /** Alt text (required for accessibility). */\n alt: string;\n /** Intrinsic width of the source image in pixels. */\n width: number;\n /** Intrinsic height of the source image in pixels. */\n height: number;\n /** Responsive widths to generate for srcset. Defaults to [width]. */\n widths?: number[];\n /** Sizes attribute for responsive layout, e.g. \"100vw\" or \"(min-width: 768px) 50vw, 100vw\". */\n sizes?: string;\n /** If true, loads eagerly with high priority (for above-the-fold images). */\n priority?: boolean;\n /** CSS class for the img element. */\n class?: string;\n /** Additional HTML attributes. */\n attributes?: Record<string, string>;\n}\n\n/**\n * Creates a ElurTemplate that renders an optimized `<img>` or `<picture>` element.\n *\n * When a manifest is active (post-build or SSR with manifest loaded):\n * * Emits `<picture>` with `<source>` per format and real hashed variant URLs.\n * * Uses real dimensions from the manifest.\n *\n * When no manifest is active (first build pass, dev without sharp):\n * * Emits a plain `<img>` with the original src.\n * * Does NOT emit srcset with unresolved variant URLs.\n * * Registers the image for build-time processing.\n */\nexport function image(opts: ImageOptions): ElurTemplate {\n const {\n src,\n alt,\n width,\n height,\n widths = [width],\n sizes,\n priority = false,\n class: className,\n attributes = {},\n } = opts;\n\n // Register for build-time processing.\n registerImage(src, widths);\n\n // If a manifest is active and has an entry for this src, emit <picture>.\n let html: string;\n const manifest = getActiveManifest();\n const entry = manifest ? getManifestEntry(manifest, src) : undefined;\n if (entry && entry.variants.length > 0) {\n html = buildPictureMarkup(entry, {\n alt,\n sizes,\n priority,\n class: className,\n attributes,\n fallbackSrc: src,\n fallbackWidth: width,\n fallbackHeight: height,\n });\n } else {\n // No manifest or no variants: emit a plain <img> with the original src.\n // Do NOT emit srcset — the variant files don't exist yet.\n const loadingAttr = priority ? \"\" : ' loading=\"lazy\"';\n const fetchPriorityAttr = priority ? ' fetchpriority=\"high\"' : \"\";\n const sizesAttr = sizes ? ` sizes=\"${escapeAttr(sizes)}\"` : \"\";\n const classAttr = className ? ` class=\"${escapeAttr(className)}\"` : \"\";\n const extraAttrs = Object.entries(attributes)\n .map(([key, value]) => ` ${escapeAttr(key)}=\"${escapeAttr(String(value))}\"`)\n .join(\"\");\n html = `<img src=\"${escapeAttr(src)}\" alt=\"${escapeAttr(alt)}\" width=\"${width}\" height=\"${height}\"${sizesAttr}${loadingAttr} decoding=\"async\"${fetchPriorityAttr}${classAttr}${extraAttrs} />`;\n }\n\n return {\n __isElurTemplate: true as const,\n [ELUR_RENDER_PROTOCOL]: {\n renderServer: () => html,\n },\n mount(container: Element | string) {\n const el = typeof container === \"string\" ? document.querySelector(container) : container;\n if (!el) throw new Error(\"[elur-kit] image(): container not found\");\n el.innerHTML = html;\n return { unmount() { el.innerHTML = \"\"; } };\n },\n _render(parent: Node, before: Node | null): () => void {\n const wrapper = document.createElement(\"template\");\n wrapper.innerHTML = html;\n const fragment = wrapper.content;\n const inserted = Array.from(fragment.childNodes);\n parent.insertBefore(fragment, before);\n return () => {\n for (const node of inserted) {\n if (node.parentNode) node.parentNode.removeChild(node);\n }\n };\n },\n } as unknown as ElurTemplate;\n}\n\nfunction escapeAttr(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n\nexport {\n processImageBatch,\n readManifest,\n writeManifest,\n getManifestEntry,\n buildSrcset,\n buildPictureMarkup,\n validateManifestUrls,\n isSharpAvailable,\n getImage,\n createImageService,\n transformHash,\n} from \"./service.js\";\nexport type {\n ImageManifest,\n ImageEntry,\n ImageVariant,\n ProcessOptions,\n ProcessResult,\n ImageRequest,\n ImageMetadata,\n GeneratedImage,\n ImageService,\n ImageServiceCapabilities,\n ImageServiceContext,\n} from \"./service.js\";\n"],"mappings":";;;;AAuDA,SAAgB,EAAM,GAAkC;CACtD,IAAM,EACJ,QACA,QACA,UACA,WACA,YAAS,CAAC,CAAK,GACf,UACA,cAAW,IACX,OAAO,GACP,gBAAa,CAAC,MACZ;CAGJ,EAAc,GAAK,CAAM;CAGzB,IAAI,GACE,IAAW,EAAkB,GAC7B,IAAQ,IAAW,EAAiB,GAAU,CAAG,IAAI,KAAA;CAC3D,IAAI,KAAS,EAAM,SAAS,SAAS,GACnC,IAAO,EAAmB,GAAO;EAC/B;EACA;EACA;EACA,OAAO;EACP;EACA,aAAa;EACb,eAAe;EACf,gBAAgB;CAClB,CAAC;MACI;EAGL,IAAM,IAAc,IAAW,KAAK,qBAC9B,IAAoB,IAAW,4BAA0B,IACzD,IAAY,IAAQ,WAAW,EAAW,CAAK,EAAE,KAAK,IACtD,IAAY,IAAY,WAAW,EAAW,CAAS,EAAE,KAAK,IAC9D,IAAa,OAAO,QAAQ,CAAU,CAAC,CAC1C,KAAK,CAAC,GAAK,OAAW,IAAI,EAAW,CAAG,EAAE,IAAI,EAAW,OAAO,CAAK,CAAC,EAAE,EAAE,CAAC,CAC3E,KAAK,EAAE;EACV,IAAO,aAAa,EAAW,CAAG,EAAE,SAAS,EAAW,CAAG,EAAE,WAAW,EAAM,YAAY,EAAO,GAAG,IAAY,EAAY,mBAAmB,IAAoB,IAAY,EAAW;CAC5L;CAEA,OAAO;EACL,kBAAkB;GACjB,IAAuB,EACtB,oBAAoB,EACtB;EACA,MAAM,GAA6B;GACjC,IAAM,IAAK,OAAO,KAAc,WAAW,SAAS,cAAc,CAAS,IAAI;GAC/E,IAAI,CAAC,GAAI,MAAU,MAAM,yCAAyC;GAElE,OADA,EAAG,YAAY,GACR,EAAE,UAAU;IAAE,EAAG,YAAY;GAAI,EAAE;EAC5C;EACA,QAAQ,GAAc,GAAiC;GACrD,IAAM,IAAU,SAAS,cAAc,UAAU;GACjD,EAAQ,YAAY;GACpB,IAAM,IAAW,EAAQ,SACnB,IAAW,MAAM,KAAK,EAAS,UAAU;GAE/C,OADA,EAAO,aAAa,GAAU,CAAM,SACvB;IACX,KAAK,IAAM,KAAQ,GACjB,AAAI,EAAK,cAAY,EAAK,WAAW,YAAY,CAAI;GAEzD;EACF;CACF;AACF;AAEA,SAAS,EAAW,GAAuB;CACzC,OAAO,EACJ,QAAQ,MAAM,OAAO,CAAC,CACtB,QAAQ,MAAM,QAAQ,CAAC,CACvB,QAAQ,MAAM,MAAM,CAAC,CACrB,QAAQ,MAAM,MAAM;AACzB"}
@@ -0,0 +1,37 @@
1
+ import type { ImageFormat } from "./index.js";
2
+ export interface PipelineOptions {
3
+ /** Absolute path to the public directory (source images live here). */
4
+ publicDir: string;
5
+ /** Absolute path to the output directory (variants are written here). */
6
+ outDir: string;
7
+ /** Formats to generate. Defaults to ["webp", "avif"]. */
8
+ formats?: ImageFormat[];
9
+ /** Quality (1-100). Defaults to 80. */
10
+ quality?: number;
11
+ }
12
+ export interface ProcessedImage {
13
+ /** Original source path (e.g. "/images/hero.jpg"). */
14
+ src: string;
15
+ /** Generated variant paths relative to outDir. */
16
+ variants: {
17
+ path: string;
18
+ width: number;
19
+ format: ImageFormat;
20
+ }[];
21
+ }
22
+ /**
23
+ * Processes a batch of images: for each image, generates variants at the
24
+ * specified widths and formats using sharp. Returns the list of generated
25
+ * files.
26
+ *
27
+ * If sharp is not installed, returns an empty array and logs a warning once.
28
+ */
29
+ export declare function processImages(images: {
30
+ src: string;
31
+ widths: number[];
32
+ formats: ImageFormat[];
33
+ }[], options: PipelineOptions): Promise<ProcessedImage[]>;
34
+ /**
35
+ * Checks whether sharp is available without actually loading it.
36
+ */
37
+ export declare function isSharpAvailable(): Promise<boolean>;
@@ -0,0 +1,37 @@
1
+ import type { ImageFormat } from "./index.js";
2
+ export interface PipelineOptions {
3
+ /** Absolute path to the public directory (source images live here). */
4
+ publicDir: string;
5
+ /** Absolute path to the output directory (variants are written here). */
6
+ outDir: string;
7
+ /** Formats to generate. Defaults to ["webp", "avif"]. */
8
+ formats?: ImageFormat[];
9
+ /** Quality (1-100). Defaults to 80. */
10
+ quality?: number;
11
+ }
12
+ export interface ProcessedImage {
13
+ /** Original source path (e.g. "/images/hero.jpg"). */
14
+ src: string;
15
+ /** Generated variant paths relative to outDir. */
16
+ variants: {
17
+ path: string;
18
+ width: number;
19
+ format: ImageFormat;
20
+ }[];
21
+ }
22
+ /**
23
+ * Processes a batch of images: for each image, generates variants at the
24
+ * specified widths and formats using sharp. Returns the list of generated
25
+ * files.
26
+ *
27
+ * If sharp is not installed, returns an empty array and logs a warning once.
28
+ */
29
+ export declare function processImages(images: {
30
+ src: string;
31
+ widths: number[];
32
+ formats: ImageFormat[];
33
+ }[], options: PipelineOptions): Promise<ProcessedImage[]>;
34
+ /**
35
+ * Checks whether sharp is available without actually loading it.
36
+ */
37
+ export declare function isSharpAvailable(): Promise<boolean>;
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=[],t=null;function n(e){t=e}function r(){return t}function i(){let t=[...e];return e.length=0,t}function a(t,n){e.find(e=>e.src===t&&e.widths.join(`,`)===n.join(`,`))||e.push({src:t,widths:n,formats:[`webp`,`avif`]})}exports.consumeImageRegistry=i,exports.getActiveManifest=r,exports.registerImage=a,exports.setImageManifest=n;
2
+ //# sourceMappingURL=registry.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.cjs","names":[],"sources":["../../../src/image/registry.ts"],"sourcesContent":["// --- Shared image registry (build-time singleton) ---\n//\n// This module owns the mutable state the image pipeline relies on:\n// * `renderRegistry` — images registered by `image()` during a render pass.\n// * `activeManifest` — the processed manifest used to emit `<picture>`.\n//\n// It MUST stay in its own chunk (`dist/lib/image/registry.js`) so that both the\n// CLI bundle (`dist/lib/cli.js`) and the library entry (`dist/lib/image/index.js`)\n// import the *same* physical module instance. If the registry state is inlined\n// into the CLI, `consumeImageRegistry()` reads a different (empty) array than\n// the one the app's `image()` calls populated, and the two-pass build silently\n// processes nothing. Both vite configs externalize this chunk to enforce that.\n\nimport type { ImageManifest } from \"./service.js\";\n\nexport type ImageFormat = \"webp\" | \"avif\" | \"jpeg\" | \"png\";\n\nexport interface RegisteredImage {\n src: string;\n widths: number[];\n formats: ImageFormat[];\n}\n\nconst renderRegistry: RegisteredImage[] = [];\n\n/**\n * The active image manifest (if loaded). When set, image() emits real\n * <picture>/<source> markup from the manifest instead of a plain <img>.\n */\nlet activeManifest: ImageManifest | null = null;\n\n/**\n * Set the active image manifest. Called by the build pipeline after\n * processing, or by the SSR server on startup.\n */\nexport function setImageManifest(manifest: ImageManifest | null): void {\n activeManifest = manifest;\n}\n\n/**\n * Returns the currently active image manifest (or null). Used by `image()`\n * to decide between `<picture>` (manifest present) and plain `<img>`.\n */\nexport function getActiveManifest(): ImageManifest | null {\n return activeManifest;\n}\n\n/**\n * Returns the images registered during the current render pass and clears\n * the registry. Called by the build pipeline after rendering all pages.\n */\nexport function consumeImageRegistry(): RegisteredImage[] {\n const items = [...renderRegistry];\n renderRegistry.length = 0;\n return items;\n}\n\n/**\n * Registers an image for build-time processing (if sharp is available).\n */\nexport function registerImage(src: string, widths: number[]): void {\n // Avoid duplicates in the same render pass.\n const existing = renderRegistry.find(\n (r) => r.src === src && r.widths.join(\",\") === widths.join(\",\"),\n );\n if (!existing) {\n renderRegistry.push({ src, widths, formats: [\"webp\", \"avif\"] });\n }\n}\n"],"mappings":"mEAuBA,IAAM,EAAoC,CAAC,EAMvC,EAAuC,KAM3C,SAAgB,EAAiB,EAAsC,CACrE,EAAiB,CACnB,CAMA,SAAgB,GAA0C,CACxD,OAAO,CACT,CAMA,SAAgB,GAA0C,CACxD,IAAM,EAAQ,CAAC,GAAG,CAAc,EAEhC,MADA,GAAe,OAAS,EACjB,CACT,CAKA,SAAgB,EAAc,EAAa,EAAwB,CAEhD,EAAe,KAC7B,GAAM,EAAE,MAAQ,GAAO,EAAE,OAAO,KAAK,GAAG,IAAM,EAAO,KAAK,GAAG,CAE3D,GACH,EAAe,KAAK,CAAE,MAAK,SAAQ,QAAS,CAAC,OAAQ,MAAM,CAAE,CAAC,CAElE"}