alepha 0.20.2 → 0.20.3

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 (208) hide show
  1. package/README.md +0 -1
  2. package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
  3. package/assets/swagger-ui/swagger-ui.css +1 -1
  4. package/dist/api/audits/index.browser.js +49 -0
  5. package/dist/api/audits/index.browser.js.map +1 -1
  6. package/dist/api/audits/index.d.ts.map +1 -1
  7. package/dist/api/audits/index.js +49 -0
  8. package/dist/api/audits/index.js.map +1 -1
  9. package/dist/api/files/index.d.ts.map +1 -1
  10. package/dist/api/files/index.js.map +1 -1
  11. package/dist/api/jobs/index.d.ts +16 -75
  12. package/dist/api/jobs/index.d.ts.map +1 -1
  13. package/dist/api/jobs/index.js.map +1 -1
  14. package/dist/api/keys/index.js.map +1 -1
  15. package/dist/api/notifications/index.d.ts +1 -10
  16. package/dist/api/notifications/index.d.ts.map +1 -1
  17. package/dist/api/organizations/index.d.ts.map +1 -1
  18. package/dist/api/parameters/index.browser.js +37 -0
  19. package/dist/api/parameters/index.browser.js.map +1 -1
  20. package/dist/api/parameters/index.d.ts +4 -65
  21. package/dist/api/parameters/index.d.ts.map +1 -1
  22. package/dist/api/parameters/index.js +37 -0
  23. package/dist/api/parameters/index.js.map +1 -1
  24. package/dist/api/payments/index.d.ts.map +1 -1
  25. package/dist/api/payments/index.js.map +1 -1
  26. package/dist/api/users/index.d.ts +207 -5184
  27. package/dist/api/users/index.d.ts.map +1 -1
  28. package/dist/api/users/index.js +2 -4
  29. package/dist/api/users/index.js.map +1 -1
  30. package/dist/api/verifications/index.d.ts.map +1 -1
  31. package/dist/api/verifications/index.js +2 -1
  32. package/dist/api/verifications/index.js.map +1 -1
  33. package/dist/bucket/index.js +5 -1
  34. package/dist/bucket/index.js.map +1 -1
  35. package/dist/bucket/index.workerd.js +5 -1
  36. package/dist/bucket/index.workerd.js.map +1 -1
  37. package/dist/cache/core/index.js.map +1 -1
  38. package/dist/cache/core/index.workerd.js.map +1 -1
  39. package/dist/captcha/index.js.map +1 -1
  40. package/dist/cli/core/index.d.ts +217 -11647
  41. package/dist/cli/core/index.d.ts.map +1 -1
  42. package/dist/cli/core/index.js +706 -42
  43. package/dist/cli/core/index.js.map +1 -1
  44. package/dist/cli/devtools/index.js +7 -1
  45. package/dist/cli/devtools/index.js.map +1 -1
  46. package/dist/cli/platform/index.d.ts +41 -64
  47. package/dist/cli/platform/index.d.ts.map +1 -1
  48. package/dist/cli/platform/index.js +47 -0
  49. package/dist/cli/platform/index.js.map +1 -1
  50. package/dist/cli/vendor/index.js +15 -0
  51. package/dist/cli/vendor/index.js.map +1 -1
  52. package/dist/command/index.js +1 -1
  53. package/dist/command/index.js.map +1 -1
  54. package/dist/core/index.browser.js.map +1 -1
  55. package/dist/core/index.d.ts +2 -8
  56. package/dist/core/index.d.ts.map +1 -1
  57. package/dist/core/index.js.map +1 -1
  58. package/dist/core/index.native.js.map +1 -1
  59. package/dist/core/index.workerd.js.map +1 -1
  60. package/dist/crypto/index.js.map +1 -1
  61. package/dist/datetime/index.js.map +1 -1
  62. package/dist/email/core/index.js.map +1 -1
  63. package/dist/email/smtp/index.js +2 -10522
  64. package/dist/email/smtp/index.js.map +1 -1
  65. package/dist/fake/index.d.ts +4 -8085
  66. package/dist/fake/index.d.ts.map +1 -1
  67. package/dist/fake/index.js +3 -33554
  68. package/dist/fake/index.js.map +1 -1
  69. package/dist/lock/core/index.js.map +1 -1
  70. package/dist/lock/redis/index.js.map +1 -1
  71. package/dist/logger/index.js +32 -1
  72. package/dist/logger/index.js.map +1 -1
  73. package/dist/mcp/index.js +5 -1
  74. package/dist/mcp/index.js.map +1 -1
  75. package/dist/orm/core/index.browser.js +1 -361
  76. package/dist/orm/core/index.browser.js.map +1 -1
  77. package/dist/orm/core/index.bun.js +14 -406
  78. package/dist/orm/core/index.bun.js.map +1 -1
  79. package/dist/orm/core/index.d.ts +96 -5117
  80. package/dist/orm/core/index.d.ts.map +1 -1
  81. package/dist/orm/core/index.js +23 -419
  82. package/dist/orm/core/index.js.map +1 -1
  83. package/dist/orm/postgres/index.bun.js +17 -20
  84. package/dist/orm/postgres/index.bun.js.map +1 -1
  85. package/dist/orm/postgres/index.d.ts +2 -613
  86. package/dist/orm/postgres/index.d.ts.map +1 -1
  87. package/dist/orm/postgres/index.js +17 -20
  88. package/dist/orm/postgres/index.js.map +1 -1
  89. package/dist/react/core/index.js.map +1 -1
  90. package/dist/react/i18n/index.js.map +1 -1
  91. package/dist/react/intro/index.js +22 -17
  92. package/dist/react/intro/index.js.map +1 -1
  93. package/dist/react/router/index.browser.js +78 -2
  94. package/dist/react/router/index.browser.js.map +1 -1
  95. package/dist/react/router/index.d.ts +22 -1
  96. package/dist/react/router/index.d.ts.map +1 -1
  97. package/dist/react/router/index.js +102 -4
  98. package/dist/react/router/index.js.map +1 -1
  99. package/dist/react/testing/index.d.ts +1 -411
  100. package/dist/react/testing/index.d.ts.map +1 -1
  101. package/dist/react/testing/index.js +13 -12293
  102. package/dist/react/testing/index.js.map +1 -1
  103. package/dist/react/ui/index.js +3 -0
  104. package/dist/react/ui/index.js.map +1 -1
  105. package/dist/react/websocket/index.js.map +1 -1
  106. package/dist/redis/index.js.map +1 -1
  107. package/dist/scheduler/index.d.ts +1 -83
  108. package/dist/scheduler/index.d.ts.map +1 -1
  109. package/dist/scheduler/index.js +2 -391
  110. package/dist/scheduler/index.js.map +1 -1
  111. package/dist/scheduler/index.workerd.js +2 -391
  112. package/dist/scheduler/index.workerd.js.map +1 -1
  113. package/dist/security/index.browser.js.map +1 -1
  114. package/dist/security/index.d.ts +2 -325
  115. package/dist/security/index.d.ts.map +1 -1
  116. package/dist/security/index.js +3 -1362
  117. package/dist/security/index.js.map +1 -1
  118. package/dist/server/auth/index.d.ts +1 -1054
  119. package/dist/server/auth/index.d.ts.map +1 -1
  120. package/dist/server/auth/index.js +16 -1224
  121. package/dist/server/auth/index.js.map +1 -1
  122. package/dist/server/cookies/index.js.map +1 -1
  123. package/dist/server/core/index.browser.js.map +1 -1
  124. package/dist/server/core/index.d.ts +1 -4
  125. package/dist/server/core/index.d.ts.map +1 -1
  126. package/dist/server/core/index.js +19 -4
  127. package/dist/server/core/index.js.map +1 -1
  128. package/dist/server/links/index.browser.js.map +1 -1
  129. package/dist/server/links/index.js.map +1 -1
  130. package/dist/server/metrics/index.d.ts +1 -514
  131. package/dist/server/metrics/index.d.ts.map +1 -1
  132. package/dist/server/metrics/index.js +4 -4356
  133. package/dist/server/metrics/index.js.map +1 -1
  134. package/dist/server/rate-limit/index.js.map +1 -1
  135. package/dist/server/static/index.js.map +1 -1
  136. package/dist/server/swagger/index.js +1 -1
  137. package/dist/server/swagger/index.js.map +1 -1
  138. package/dist/sms/index.js.map +1 -1
  139. package/dist/system/index.browser.js.map +1 -1
  140. package/dist/system/index.js.map +1 -1
  141. package/dist/system/index.workerd.js.map +1 -1
  142. package/dist/topic/core/index.js.map +1 -1
  143. package/dist/websocket/index.browser.js +21 -0
  144. package/dist/websocket/index.browser.js.map +1 -1
  145. package/dist/websocket/index.js +21 -0
  146. package/dist/websocket/index.js.map +1 -1
  147. package/package.json +18 -15
  148. package/src/api/files/__tests__/FileController.spec.ts +1 -1
  149. package/src/api/jobs/__tests__/$job.spec.ts +5 -1
  150. package/src/api/users/schemas/userQuerySchema.ts +0 -1
  151. package/src/api/users/services/UserService.ts +1 -5
  152. package/src/api/verifications/__tests__/CodeVerification.spec.ts +14 -0
  153. package/src/api/verifications/__tests__/LinkVerification.spec.ts +14 -0
  154. package/src/api/verifications/services/VerificationService.ts +1 -0
  155. package/src/cli/core/__tests__/init.spec.ts +208 -0
  156. package/src/cli/core/commands/init.ts +12 -0
  157. package/src/cli/core/services/PackageManagerUtils.ts +23 -6
  158. package/src/cli/core/services/ProjectScaffolder.ts +298 -20
  159. package/src/cli/core/tasks/BuildDockerTask.ts +9 -10
  160. package/src/cli/core/tasks/BuildServerTask.ts +8 -0
  161. package/src/cli/core/templates/apiIndexTs.ts +23 -1
  162. package/src/cli/core/templates/componentsJsonTs.ts +39 -0
  163. package/src/cli/core/templates/mainCss.ts +1 -0
  164. package/src/cli/core/templates/saasAdminLayoutTsx.ts +77 -0
  165. package/src/cli/core/templates/saasAdminPagesTsx.ts +26 -0
  166. package/src/cli/core/templates/saasAuthLayoutTsx.ts +20 -0
  167. package/src/cli/core/templates/saasAuthPagesTsx.ts +62 -0
  168. package/src/cli/core/templates/saasRealmProviderTs.ts +46 -0
  169. package/src/cli/core/templates/webAppRouterTs.ts +104 -1
  170. package/src/cli/core/templates/webIndexTs.ts +23 -1
  171. package/src/cli/platform/__tests__/SecretsCommand.spec.ts +2 -0
  172. package/src/command/providers/CliProvider.ts +1 -1
  173. package/src/core/interfaces/Service.ts +3 -1
  174. package/src/core/providers/TypeProvider.ts +1 -1
  175. package/src/logger/services/Logger.ts +1 -1
  176. package/src/mcp/__tests__/$resource.spec.ts +1 -1
  177. package/src/mcp/__tests__/$tool.spec.ts +1 -1
  178. package/src/mcp/__tests__/McpServerProvider.spec.ts +1 -1
  179. package/src/orm/__tests__/$repository-tests.ts +1 -0
  180. package/src/orm/__tests__/orm-next-tests.ts +2 -67
  181. package/src/orm/__tests__/orm-next.spec.ts +0 -21
  182. package/src/orm/core/index.shared.ts +0 -2
  183. package/src/orm/core/index.ts +1 -2
  184. package/src/orm/core/primitives/$repository.ts +3 -6
  185. package/src/orm/core/providers/drivers/DatabaseProvider.ts +0 -5
  186. package/src/orm/core/providers/drivers/NodeSqliteProvider.ts +11 -13
  187. package/src/orm/core/services/ModelBuilder.ts +1 -13
  188. package/src/orm/core/services/Repository.ts +1 -42
  189. package/src/orm/core/services/SqliteModelBuilder.ts +2 -33
  190. package/src/orm/postgres/services/PostgresModelBuilder.ts +10 -45
  191. package/src/react/intro/components/GettingStartedAuthSlide.tsx +11 -4
  192. package/src/react/router/__tests__/ReactBrowserProvider.browser.spec.ts +213 -2
  193. package/src/react/router/providers/ReactBrowserProvider.ts +73 -0
  194. package/src/react/router/providers/ReactBrowserRouterProvider.ts +1 -1
  195. package/src/react/router/providers/ReactPreloadProvider.ts +1 -1
  196. package/src/react/router/providers/ReactServerProvider.ts +1 -0
  197. package/src/scheduler/providers/CronProvider.ts +1 -1
  198. package/src/security/primitives/$basicAuth.ts +1 -1
  199. package/src/server/auth/providers/ServerAuthProvider.ts +5 -1
  200. package/src/server/core/interfaces/ServerRequest.ts +1 -0
  201. package/src/server/core/providers/ServerProvider.ts +1 -1
  202. package/src/server/core/providers/ServerRouterProvider.ts +2 -2
  203. package/src/server/core/services/HttpClient.ts +1 -1
  204. package/src/server/swagger/providers/ServerSwaggerProvider.ts +1 -1
  205. package/dist/react/testing/chunk-DBEY4PJZ.js +0 -16
  206. package/src/orm/core/__tests__/parseQueryString.spec.ts +0 -196
  207. package/src/orm/core/helpers/parseQueryString.ts +0 -502
  208. package/src/orm/core/primitives/$view.ts +0 -88
@@ -13,12 +13,15 @@ import { useStore } from "alepha/react";
13
13
  const uiAtom = $atom({
14
14
  name: "alepha.react.ui",
15
15
  schema: t.object({
16
+ /** Color mode preference. `"system"` follows the OS-level setting. */
16
17
  mode: t.enum([
17
18
  "light",
18
19
  "dark",
19
20
  "system"
20
21
  ]),
22
+ /** Theme palette name. UI consumers map this to a CSS class on the root. */
21
23
  theme: t.string(),
24
+ /** Sidebar UI state. */
22
25
  sidebar: t.object({ collapsed: t.boolean() })
23
26
  }),
24
27
  default: {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/react/ui/atoms/uiAtom.ts","../../../src/react/ui/services/UiPersistence.ts","../../../src/react/ui/hooks/useColorMode.ts","../../../src/react/ui/hooks/useTheme.ts","../../../src/react/ui/components/ColorScheme.tsx","../../../src/react/ui/hooks/useSidebarState.ts","../../../src/react/ui/index.ts"],"sourcesContent":["import { $atom, type Static, t } from \"alepha\";\n\n/**\n * Persisted UI state — color mode, theme palette, sidebar collapsed state, etc.\n *\n * The atom is bound to a single `alepha-ui` cookie via {@link UiPersistence},\n * so values survive page reloads and are available during SSR.\n */\nexport const uiAtom = $atom({\n name: \"alepha.react.ui\",\n schema: t.object({\n /** Color mode preference. `\"system\"` follows the OS-level setting. */\n mode: t.enum([\"light\", \"dark\", \"system\"]),\n /** Theme palette name. UI consumers map this to a CSS class on the root. */\n theme: t.string(),\n /** Sidebar UI state. */\n sidebar: t.object({\n collapsed: t.boolean(),\n }),\n }),\n default: {\n mode: \"system\",\n theme: \"default\",\n sidebar: { collapsed: false },\n },\n});\n\nexport type UiState = Static<typeof uiAtom.schema>;\n","import { $head } from \"alepha/react/head\";\nimport { $cookie } from \"alepha/server/cookies\";\nimport { uiAtom } from \"../atoms/uiAtom.ts\";\n\n/**\n * Inline `<script>` rendered by SSR into the document `<head>`. Runs\n * synchronously before any CSS or React hydration: reads the `alepha-ui`\n * cookie, resolves `mode === \"system\"` via `prefers-color-scheme`, and\n * applies `class=\"dark\"` (and optional `theme-<name>`) on `<html>`.\n *\n * This is what kills the flash-of-wrong-theme (FOUC) you'd otherwise get\n * with React-effect-based theming. Failures are swallowed silently — at\n * worst the page paints in light mode for one frame.\n */\nconst colorSchemeBoot = `(function(){try{var m=document.cookie.match(/(?:^|;\\\\s*)alepha-ui=([^;]+)/);var s=m?JSON.parse(decodeURIComponent(m[1])):{};var mode=s.mode||\"system\";var dark=mode===\"dark\"||(mode===\"system\"&&window.matchMedia&&window.matchMedia(\"(prefers-color-scheme: dark)\").matches);var r=document.documentElement;if(dark)r.classList.add(\"dark\");if(s.theme&&s.theme!==\"default\")r.classList.add(\"theme-\"+s.theme);}catch(e){}})();`;\n\n/**\n * Binds the {@link uiAtom} to an `alepha-ui` cookie + injects an inline\n * boot script into the SSR head to prevent FOUC on first paint.\n *\n * Reading flow: on app boot the cookie is parsed and pushed into the atom\n * (via the `key` option on `$cookie`). Writing flow: every time the atom\n * mutates, the cookie is rewritten — a single `useStore(uiAtom)` call is\n * enough to persist UI preferences across reloads.\n *\n * Persists for 365 days; SameSite=lax so it travels on top-level navigation\n * but not on cross-origin requests.\n */\nexport class UiPersistence {\n ui = $cookie({\n name: \"alepha-ui\",\n key: uiAtom.key,\n schema: uiAtom.schema,\n ttl: [365, \"days\"],\n sameSite: \"lax\",\n });\n\n head = $head({\n script: [{ content: colorSchemeBoot }],\n });\n}\n","import { useStore } from \"alepha/react\";\nimport { useEffect, useState } from \"react\";\nimport { uiAtom } from \"../atoms/uiAtom.ts\";\n\nexport type ColorMode = \"light\" | \"dark\" | \"system\";\nexport type ResolvedColorMode = \"light\" | \"dark\";\n\n/**\n * Read and update the user's color-mode preference. `\"system\"` resolves to\n * the OS preference and updates live as the OS toggles between light/dark.\n *\n * @example\n * const { mode, setMode, resolved } = useColorMode();\n * setMode(\"dark\");\n * document.documentElement.classList.toggle(\"dark\", resolved === \"dark\");\n */\nexport const useColorMode = () => {\n const [state, set] = useStore(uiAtom);\n const mode = (state?.mode ?? \"system\") as ColorMode;\n const resolved = useResolvedColorMode(mode);\n\n return {\n mode,\n resolved,\n setMode: (next: ColorMode) => {\n set({ ...(state ?? uiAtom.options.default!), mode: next });\n },\n };\n};\n\nconst useResolvedColorMode = (mode: ColorMode): ResolvedColorMode => {\n const [systemDark, setSystemDark] = useState<boolean>(() => {\n if (typeof window === \"undefined\") return false;\n return window.matchMedia?.(\"(prefers-color-scheme: dark)\").matches ?? false;\n });\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const mq = window.matchMedia?.(\"(prefers-color-scheme: dark)\");\n if (!mq) return;\n const onChange = (ev: MediaQueryListEvent) => setSystemDark(ev.matches);\n mq.addEventListener(\"change\", onChange);\n return () => mq.removeEventListener(\"change\", onChange);\n }, []);\n\n if (mode === \"dark\") return \"dark\";\n if (mode === \"light\") return \"light\";\n return systemDark ? \"dark\" : \"light\";\n};\n","import { useStore } from \"alepha/react\";\nimport { uiAtom } from \"../atoms/uiAtom.ts\";\n\n/**\n * Read and update the active theme palette name. UI consumers typically map\n * the value to a class on the document root (e.g. `theme-claude`).\n *\n * @example\n * const { theme, setTheme } = useTheme();\n * setTheme(\"claude\");\n */\nexport const useTheme = () => {\n const [state, set] = useStore(uiAtom);\n const theme = state?.theme ?? \"default\";\n\n return {\n theme,\n setTheme: (next: string) => {\n set({ ...(state ?? uiAtom.options.default!), theme: next });\n },\n };\n};\n","import { useEffect } from \"react\";\nimport { useColorMode } from \"../hooks/useColorMode.ts\";\nimport { useTheme } from \"../hooks/useTheme.ts\";\n\n/**\n * Applies `class=\"dark\"` and an optional theme palette class\n * (`theme-<name>`) to the document root, syncing whenever the underlying\n * atom mutates.\n *\n * Mount once near the root of your tree (typically inside the layout).\n *\n * @example\n * <ColorScheme />\n */\nexport function ColorScheme() {\n const { resolved } = useColorMode();\n const { theme } = useTheme();\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n document.documentElement.classList.toggle(\"dark\", resolved === \"dark\");\n }, [resolved]);\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n const root = document.documentElement;\n const previous: string[] = [];\n root.classList.forEach((cls) => {\n if (cls.startsWith(\"theme-\")) previous.push(cls);\n });\n for (const cls of previous) root.classList.remove(cls);\n if (theme && theme !== \"default\") root.classList.add(`theme-${theme}`);\n }, [theme]);\n\n return null;\n}\n","import { useStore } from \"alepha/react\";\nimport { uiAtom } from \"../atoms/uiAtom.ts\";\n\n/**\n * Read and update the sidebar collapsed state. The value is persisted via the\n * `alepha-ui` cookie so it survives reloads and is available during SSR — no\n * flash of expanded-then-collapsed when the user prefers a collapsed shell.\n *\n * @example\n * const { collapsed, setCollapsed, toggle } = useSidebarState();\n */\nexport const useSidebarState = () => {\n const [state, set] = useStore(uiAtom);\n const collapsed = state?.sidebar.collapsed ?? false;\n\n const setCollapsed = (next: boolean) => {\n const base = state ?? uiAtom.options.default!;\n set({ ...base, sidebar: { ...base.sidebar, collapsed: next } });\n };\n\n return {\n collapsed,\n setCollapsed,\n toggle: () => setCollapsed(!collapsed),\n };\n};\n","import { $module } from \"alepha\";\nimport type { UiState } from \"./atoms/uiAtom.ts\";\nimport { UiPersistence } from \"./services/UiPersistence.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./atoms/uiAtom.ts\";\nexport * from \"./components/ColorScheme.tsx\";\nexport * from \"./hooks/useColorMode.ts\";\nexport * from \"./hooks/useSidebarState.ts\";\nexport * from \"./hooks/useTheme.ts\";\nexport * from \"./services/UiPersistence.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n export interface State {\n \"alepha.react.ui\": UiState;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Persisted UI state: color mode, theme palette, sidebar collapsed state.\n *\n * Backed by an `alepha-ui` cookie so preferences survive reloads and are\n * available during SSR (no flash of wrong theme).\n *\n * @module alepha.react.ui\n */\nexport const AlephaReactUi = $module({\n name: \"alepha.react.ui\",\n services: [UiPersistence],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/react/ui/atoms/uiAtom.ts","../../../src/react/ui/services/UiPersistence.ts","../../../src/react/ui/hooks/useColorMode.ts","../../../src/react/ui/hooks/useTheme.ts","../../../src/react/ui/components/ColorScheme.tsx","../../../src/react/ui/hooks/useSidebarState.ts","../../../src/react/ui/index.ts"],"sourcesContent":["import { $atom, type Static, t } from \"alepha\";\n\n/**\n * Persisted UI state — color mode, theme palette, sidebar collapsed state, etc.\n *\n * The atom is bound to a single `alepha-ui` cookie via {@link UiPersistence},\n * so values survive page reloads and are available during SSR.\n */\nexport const uiAtom = $atom({\n name: \"alepha.react.ui\",\n schema: t.object({\n /** Color mode preference. `\"system\"` follows the OS-level setting. */\n mode: t.enum([\"light\", \"dark\", \"system\"]),\n /** Theme palette name. UI consumers map this to a CSS class on the root. */\n theme: t.string(),\n /** Sidebar UI state. */\n sidebar: t.object({\n collapsed: t.boolean(),\n }),\n }),\n default: {\n mode: \"system\",\n theme: \"default\",\n sidebar: { collapsed: false },\n },\n});\n\nexport type UiState = Static<typeof uiAtom.schema>;\n","import { $head } from \"alepha/react/head\";\nimport { $cookie } from \"alepha/server/cookies\";\nimport { uiAtom } from \"../atoms/uiAtom.ts\";\n\n/**\n * Inline `<script>` rendered by SSR into the document `<head>`. Runs\n * synchronously before any CSS or React hydration: reads the `alepha-ui`\n * cookie, resolves `mode === \"system\"` via `prefers-color-scheme`, and\n * applies `class=\"dark\"` (and optional `theme-<name>`) on `<html>`.\n *\n * This is what kills the flash-of-wrong-theme (FOUC) you'd otherwise get\n * with React-effect-based theming. Failures are swallowed silently — at\n * worst the page paints in light mode for one frame.\n */\nconst colorSchemeBoot = `(function(){try{var m=document.cookie.match(/(?:^|;\\\\s*)alepha-ui=([^;]+)/);var s=m?JSON.parse(decodeURIComponent(m[1])):{};var mode=s.mode||\"system\";var dark=mode===\"dark\"||(mode===\"system\"&&window.matchMedia&&window.matchMedia(\"(prefers-color-scheme: dark)\").matches);var r=document.documentElement;if(dark)r.classList.add(\"dark\");if(s.theme&&s.theme!==\"default\")r.classList.add(\"theme-\"+s.theme);}catch(e){}})();`;\n\n/**\n * Binds the {@link uiAtom} to an `alepha-ui` cookie + injects an inline\n * boot script into the SSR head to prevent FOUC on first paint.\n *\n * Reading flow: on app boot the cookie is parsed and pushed into the atom\n * (via the `key` option on `$cookie`). Writing flow: every time the atom\n * mutates, the cookie is rewritten — a single `useStore(uiAtom)` call is\n * enough to persist UI preferences across reloads.\n *\n * Persists for 365 days; SameSite=lax so it travels on top-level navigation\n * but not on cross-origin requests.\n */\nexport class UiPersistence {\n ui = $cookie({\n name: \"alepha-ui\",\n key: uiAtom.key,\n schema: uiAtom.schema,\n ttl: [365, \"days\"],\n sameSite: \"lax\",\n });\n\n head = $head({\n script: [{ content: colorSchemeBoot }],\n });\n}\n","import { useStore } from \"alepha/react\";\nimport { useEffect, useState } from \"react\";\nimport { uiAtom } from \"../atoms/uiAtom.ts\";\n\nexport type ColorMode = \"light\" | \"dark\" | \"system\";\nexport type ResolvedColorMode = \"light\" | \"dark\";\n\n/**\n * Read and update the user's color-mode preference. `\"system\"` resolves to\n * the OS preference and updates live as the OS toggles between light/dark.\n *\n * @example\n * const { mode, setMode, resolved } = useColorMode();\n * setMode(\"dark\");\n * document.documentElement.classList.toggle(\"dark\", resolved === \"dark\");\n */\nexport const useColorMode = () => {\n const [state, set] = useStore(uiAtom);\n const mode = (state?.mode ?? \"system\") as ColorMode;\n const resolved = useResolvedColorMode(mode);\n\n return {\n mode,\n resolved,\n setMode: (next: ColorMode) => {\n set({ ...(state ?? uiAtom.options.default!), mode: next });\n },\n };\n};\n\nconst useResolvedColorMode = (mode: ColorMode): ResolvedColorMode => {\n const [systemDark, setSystemDark] = useState<boolean>(() => {\n if (typeof window === \"undefined\") return false;\n return window.matchMedia?.(\"(prefers-color-scheme: dark)\").matches ?? false;\n });\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const mq = window.matchMedia?.(\"(prefers-color-scheme: dark)\");\n if (!mq) return;\n const onChange = (ev: MediaQueryListEvent) => setSystemDark(ev.matches);\n mq.addEventListener(\"change\", onChange);\n return () => mq.removeEventListener(\"change\", onChange);\n }, []);\n\n if (mode === \"dark\") return \"dark\";\n if (mode === \"light\") return \"light\";\n return systemDark ? \"dark\" : \"light\";\n};\n","import { useStore } from \"alepha/react\";\nimport { uiAtom } from \"../atoms/uiAtom.ts\";\n\n/**\n * Read and update the active theme palette name. UI consumers typically map\n * the value to a class on the document root (e.g. `theme-claude`).\n *\n * @example\n * const { theme, setTheme } = useTheme();\n * setTheme(\"claude\");\n */\nexport const useTheme = () => {\n const [state, set] = useStore(uiAtom);\n const theme = state?.theme ?? \"default\";\n\n return {\n theme,\n setTheme: (next: string) => {\n set({ ...(state ?? uiAtom.options.default!), theme: next });\n },\n };\n};\n","import { useEffect } from \"react\";\nimport { useColorMode } from \"../hooks/useColorMode.ts\";\nimport { useTheme } from \"../hooks/useTheme.ts\";\n\n/**\n * Applies `class=\"dark\"` and an optional theme palette class\n * (`theme-<name>`) to the document root, syncing whenever the underlying\n * atom mutates.\n *\n * Mount once near the root of your tree (typically inside the layout).\n *\n * @example\n * <ColorScheme />\n */\nexport function ColorScheme() {\n const { resolved } = useColorMode();\n const { theme } = useTheme();\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n document.documentElement.classList.toggle(\"dark\", resolved === \"dark\");\n }, [resolved]);\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n const root = document.documentElement;\n const previous: string[] = [];\n root.classList.forEach((cls) => {\n if (cls.startsWith(\"theme-\")) previous.push(cls);\n });\n for (const cls of previous) root.classList.remove(cls);\n if (theme && theme !== \"default\") root.classList.add(`theme-${theme}`);\n }, [theme]);\n\n return null;\n}\n","import { useStore } from \"alepha/react\";\nimport { uiAtom } from \"../atoms/uiAtom.ts\";\n\n/**\n * Read and update the sidebar collapsed state. The value is persisted via the\n * `alepha-ui` cookie so it survives reloads and is available during SSR — no\n * flash of expanded-then-collapsed when the user prefers a collapsed shell.\n *\n * @example\n * const { collapsed, setCollapsed, toggle } = useSidebarState();\n */\nexport const useSidebarState = () => {\n const [state, set] = useStore(uiAtom);\n const collapsed = state?.sidebar.collapsed ?? false;\n\n const setCollapsed = (next: boolean) => {\n const base = state ?? uiAtom.options.default!;\n set({ ...base, sidebar: { ...base.sidebar, collapsed: next } });\n };\n\n return {\n collapsed,\n setCollapsed,\n toggle: () => setCollapsed(!collapsed),\n };\n};\n","import { $module } from \"alepha\";\nimport type { UiState } from \"./atoms/uiAtom.ts\";\nimport { UiPersistence } from \"./services/UiPersistence.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./atoms/uiAtom.ts\";\nexport * from \"./components/ColorScheme.tsx\";\nexport * from \"./hooks/useColorMode.ts\";\nexport * from \"./hooks/useSidebarState.ts\";\nexport * from \"./hooks/useTheme.ts\";\nexport * from \"./services/UiPersistence.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n export interface State {\n \"alepha.react.ui\": UiState;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Persisted UI state: color mode, theme palette, sidebar collapsed state.\n *\n * Backed by an `alepha-ui` cookie so preferences survive reloads and are\n * available during SSR (no flash of wrong theme).\n *\n * @module alepha.react.ui\n */\nexport const AlephaReactUi = $module({\n name: \"alepha.react.ui\",\n services: [UiPersistence],\n});\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/react/websocket/hooks/useRoom.tsx"],"sourcesContent":["import type { Static } from \"alepha\";\nimport { useAlepha, useInject } from \"alepha/react\";\nimport type { ChannelPrimitive, TWSObject } from \"alepha/websocket\";\nimport { WebSocketClient } from \"alepha/websocket\";\nimport { useEffect, useRef, useState } from \"react\";\n\n/**\n * UseRoom options\n */\nexport interface UseRoomOptions<\n TClient extends TWSObject,\n TServer extends TWSObject,\n> {\n /**\n * Room ID to connect to\n */\n roomId: string;\n\n /**\n * Channel primitive defining the schemas\n */\n channel: ChannelPrimitive<TClient, TServer>;\n\n /**\n * Handler for incoming messages from the server\n */\n handler: (message: Static<TClient>) => void;\n\n /**\n * Optional WebSocket URL override\n * Defaults to auto-detected URL based on window.location\n */\n url?: string;\n\n /**\n * Enable automatic reconnection on disconnect\n * @default true\n */\n autoReconnect?: boolean;\n\n /**\n * Reconnection interval in milliseconds\n * @default 3000\n */\n reconnectInterval?: number;\n\n /**\n * Maximum reconnection attempts (-1 for infinite)\n * @default 10\n */\n maxReconnectAttempts?: number;\n\n /**\n * Called when connection is established\n */\n onConnect?: () => void;\n\n /**\n * Called when connection is closed\n */\n onDisconnect?: () => void;\n\n /**\n * Called on connection error\n */\n onError?: (error: Error) => void;\n}\n\n/**\n * UseRoom return value\n */\nexport interface UseRoomReturn<TServer extends TWSObject> {\n /**\n * Send a message to the server\n */\n send: (message: Static<TServer>) => Promise<void>;\n\n /**\n * Whether the connection is established\n */\n isConnected: boolean;\n\n /**\n * Whether the connection is in progress\n */\n isConnecting: boolean;\n\n /**\n * Whether there was an error\n */\n isError: boolean;\n\n /**\n * The error object if any\n */\n error?: Error;\n\n /**\n * Manually reconnect\n */\n reconnect: () => void;\n\n /**\n * Manually disconnect\n */\n disconnect: () => void;\n}\n\n/**\n * React hook for WebSocket room communication\n *\n * Provides automatic connection management, reconnection, and type-safe messaging\n * for WebSocket rooms using the injected WebSocketClient service.\n *\n * Multiple useRoom hooks on the same channel will share a single WebSocket connection.\n *\n * @example\n * ```tsx\n * const chat = useRoom({\n * roomId: \"room-123\",\n * channel: chatChannel,\n * handler: (message) => {\n * if (message.type === \"append\") {\n * setMessages(prev => [...prev, message]);\n * }\n * }\n * }, [roomId]);\n *\n * const sendMessage = async () => {\n * await chat.send({\n * content: \"Hello, world!\"\n * });\n * };\n * ```\n */\nexport const useRoom = <TClient extends TWSObject, TServer extends TWSObject>(\n options: UseRoomOptions<TClient, TServer>,\n deps: unknown[],\n): UseRoomReturn<TServer> => {\n const webSocketClient = useInject(WebSocketClient);\n const unsubscribeRef = useRef<(() => void) | null>(null);\n\n const [isConnected, setIsConnected] = useState(false);\n const [isConnecting, setIsConnecting] = useState(false);\n const [isError, setIsError] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n\n const {\n roomId,\n channel,\n handler,\n url,\n autoReconnect,\n reconnectInterval,\n maxReconnectAttempts,\n onConnect,\n onDisconnect,\n onError,\n } = options;\n\n // Keep handler ref stable to avoid stale closures\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n\n useEffect(() => {\n // Subscribe to room — use ref so handler is always current\n const unsubscribe = webSocketClient.subscribe(\n roomId,\n channel,\n (msg) => handlerRef.current(msg),\n {\n url,\n autoReconnect,\n reconnectInterval,\n maxReconnectAttempts,\n onConnect: () => {\n setIsConnected(true);\n setIsConnecting(false);\n setIsError(false);\n setError(undefined);\n onConnect?.();\n },\n onDisconnect: () => {\n setIsConnected(false);\n setIsConnecting(false);\n onDisconnect?.();\n },\n onError: (err) => {\n setIsError(true);\n setError(err);\n setIsConnecting(false);\n onError?.(err);\n },\n },\n );\n\n unsubscribeRef.current = unsubscribe;\n\n // Get initial state from connection\n const connection = webSocketClient.getConnection(channel);\n if (connection) {\n setIsConnected(connection.isConnected);\n setIsConnecting(connection.isConnecting);\n setIsError(connection.isError);\n setError(connection.error);\n }\n\n // Cleanup on unmount or deps change\n return () => {\n unsubscribe();\n unsubscribeRef.current = null;\n };\n }, deps);\n\n const alepha = useAlepha();\n\n if (!alepha.isBrowser()) {\n return {\n send: async (_message: Static<TServer>) => {\n // No-op on server\n },\n isConnected: false,\n isConnecting: false,\n isError: false,\n error: undefined,\n reconnect: () => {\n // No-op on server\n },\n disconnect: () => {\n // No-op on server\n },\n };\n }\n\n return {\n send: async (message: Static<TServer>) => {\n await webSocketClient.send(roomId, channel, message);\n },\n isConnected,\n isConnecting,\n isError,\n error,\n reconnect: () => {\n const connection = webSocketClient.getConnection(channel);\n connection?.reconnect();\n },\n disconnect: () => {\n unsubscribeRef.current?.();\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuIA,MAAa,WACX,SACA,SAC2B;CAC3B,MAAM,kBAAkB,UAAU,gBAAgB;CAClD,MAAM,iBAAiB,OAA4B,KAAK;CAExD,MAAM,CAAC,aAAa,kBAAkB,SAAS,MAAM;CACrD,MAAM,CAAC,cAAc,mBAAmB,SAAS,MAAM;CACvD,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAC7C,MAAM,CAAC,OAAO,YAAY,SAA4B,KAAA,EAAU;CAEhE,MAAM,EACJ,QACA,SACA,SACA,KACA,eACA,mBACA,sBACA,WACA,cACA,YACE;CAGJ,MAAM,aAAa,OAAO,QAAQ;AAClC,YAAW,UAAU;AAErB,iBAAgB;EAEd,MAAM,cAAc,gBAAgB,UAClC,QACA,UACC,QAAQ,WAAW,QAAQ,IAAI,EAChC;GACE;GACA;GACA;GACA;GACA,iBAAiB;AACf,mBAAe,KAAK;AACpB,oBAAgB,MAAM;AACtB,eAAW,MAAM;AACjB,aAAS,KAAA,EAAU;AACnB,iBAAa;;GAEf,oBAAoB;AAClB,mBAAe,MAAM;AACrB,oBAAgB,MAAM;AACtB,oBAAgB;;GAElB,UAAU,QAAQ;AAChB,eAAW,KAAK;AAChB,aAAS,IAAI;AACb,oBAAgB,MAAM;AACtB,cAAU,IAAI;;GAEjB,CACF;AAED,iBAAe,UAAU;EAGzB,MAAM,aAAa,gBAAgB,cAAc,QAAQ;AACzD,MAAI,YAAY;AACd,kBAAe,WAAW,YAAY;AACtC,mBAAgB,WAAW,aAAa;AACxC,cAAW,WAAW,QAAQ;AAC9B,YAAS,WAAW,MAAM;;AAI5B,eAAa;AACX,gBAAa;AACb,kBAAe,UAAU;;IAE1B,KAAK;AAIR,KAAI,CAFW,WAAW,CAEd,WAAW,CACrB,QAAO;EACL,MAAM,OAAO,aAA8B;EAG3C,aAAa;EACb,cAAc;EACd,SAAS;EACT,OAAO,KAAA;EACP,iBAAiB;EAGjB,kBAAkB;EAGnB;AAGH,QAAO;EACL,MAAM,OAAO,YAA6B;AACxC,SAAM,gBAAgB,KAAK,QAAQ,SAAS,QAAQ;;EAEtD;EACA;EACA;EACA;EACA,iBAAiB;AACI,mBAAgB,cAAc,QAAQ,EAC7C,WAAW;;EAEzB,kBAAkB;AAChB,kBAAe,WAAW;;EAE7B"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/react/websocket/hooks/useRoom.tsx"],"sourcesContent":["import type { Static } from \"alepha\";\nimport { useAlepha, useInject } from \"alepha/react\";\nimport type { ChannelPrimitive, TWSObject } from \"alepha/websocket\";\nimport { WebSocketClient } from \"alepha/websocket\";\nimport { useEffect, useRef, useState } from \"react\";\n\n/**\n * UseRoom options\n */\nexport interface UseRoomOptions<\n TClient extends TWSObject,\n TServer extends TWSObject,\n> {\n /**\n * Room ID to connect to\n */\n roomId: string;\n\n /**\n * Channel primitive defining the schemas\n */\n channel: ChannelPrimitive<TClient, TServer>;\n\n /**\n * Handler for incoming messages from the server\n */\n handler: (message: Static<TClient>) => void;\n\n /**\n * Optional WebSocket URL override\n * Defaults to auto-detected URL based on window.location\n */\n url?: string;\n\n /**\n * Enable automatic reconnection on disconnect\n * @default true\n */\n autoReconnect?: boolean;\n\n /**\n * Reconnection interval in milliseconds\n * @default 3000\n */\n reconnectInterval?: number;\n\n /**\n * Maximum reconnection attempts (-1 for infinite)\n * @default 10\n */\n maxReconnectAttempts?: number;\n\n /**\n * Called when connection is established\n */\n onConnect?: () => void;\n\n /**\n * Called when connection is closed\n */\n onDisconnect?: () => void;\n\n /**\n * Called on connection error\n */\n onError?: (error: Error) => void;\n}\n\n/**\n * UseRoom return value\n */\nexport interface UseRoomReturn<TServer extends TWSObject> {\n /**\n * Send a message to the server\n */\n send: (message: Static<TServer>) => Promise<void>;\n\n /**\n * Whether the connection is established\n */\n isConnected: boolean;\n\n /**\n * Whether the connection is in progress\n */\n isConnecting: boolean;\n\n /**\n * Whether there was an error\n */\n isError: boolean;\n\n /**\n * The error object if any\n */\n error?: Error;\n\n /**\n * Manually reconnect\n */\n reconnect: () => void;\n\n /**\n * Manually disconnect\n */\n disconnect: () => void;\n}\n\n/**\n * React hook for WebSocket room communication\n *\n * Provides automatic connection management, reconnection, and type-safe messaging\n * for WebSocket rooms using the injected WebSocketClient service.\n *\n * Multiple useRoom hooks on the same channel will share a single WebSocket connection.\n *\n * @example\n * ```tsx\n * const chat = useRoom({\n * roomId: \"room-123\",\n * channel: chatChannel,\n * handler: (message) => {\n * if (message.type === \"append\") {\n * setMessages(prev => [...prev, message]);\n * }\n * }\n * }, [roomId]);\n *\n * const sendMessage = async () => {\n * await chat.send({\n * content: \"Hello, world!\"\n * });\n * };\n * ```\n */\nexport const useRoom = <TClient extends TWSObject, TServer extends TWSObject>(\n options: UseRoomOptions<TClient, TServer>,\n deps: unknown[],\n): UseRoomReturn<TServer> => {\n const webSocketClient = useInject(WebSocketClient);\n const unsubscribeRef = useRef<(() => void) | null>(null);\n\n const [isConnected, setIsConnected] = useState(false);\n const [isConnecting, setIsConnecting] = useState(false);\n const [isError, setIsError] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n\n const {\n roomId,\n channel,\n handler,\n url,\n autoReconnect,\n reconnectInterval,\n maxReconnectAttempts,\n onConnect,\n onDisconnect,\n onError,\n } = options;\n\n // Keep handler ref stable to avoid stale closures\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n\n useEffect(() => {\n // Subscribe to room — use ref so handler is always current\n const unsubscribe = webSocketClient.subscribe(\n roomId,\n channel,\n (msg) => handlerRef.current(msg),\n {\n url,\n autoReconnect,\n reconnectInterval,\n maxReconnectAttempts,\n onConnect: () => {\n setIsConnected(true);\n setIsConnecting(false);\n setIsError(false);\n setError(undefined);\n onConnect?.();\n },\n onDisconnect: () => {\n setIsConnected(false);\n setIsConnecting(false);\n onDisconnect?.();\n },\n onError: (err) => {\n setIsError(true);\n setError(err);\n setIsConnecting(false);\n onError?.(err);\n },\n },\n );\n\n unsubscribeRef.current = unsubscribe;\n\n // Get initial state from connection\n const connection = webSocketClient.getConnection(channel);\n if (connection) {\n setIsConnected(connection.isConnected);\n setIsConnecting(connection.isConnecting);\n setIsError(connection.isError);\n setError(connection.error);\n }\n\n // Cleanup on unmount or deps change\n return () => {\n unsubscribe();\n unsubscribeRef.current = null;\n };\n }, deps);\n\n const alepha = useAlepha();\n\n if (!alepha.isBrowser()) {\n return {\n send: async (_message: Static<TServer>) => {\n // No-op on server\n },\n isConnected: false,\n isConnecting: false,\n isError: false,\n error: undefined,\n reconnect: () => {\n // No-op on server\n },\n disconnect: () => {\n // No-op on server\n },\n };\n }\n\n return {\n send: async (message: Static<TServer>) => {\n await webSocketClient.send(roomId, channel, message);\n },\n isConnected,\n isConnecting,\n isError,\n error,\n reconnect: () => {\n const connection = webSocketClient.getConnection(channel);\n connection?.reconnect();\n },\n disconnect: () => {\n unsubscribeRef.current?.();\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuIA,MAAa,WACX,SACA,SAC2B;CAC3B,MAAM,kBAAkB,UAAU,gBAAgB;CAClD,MAAM,iBAAiB,OAA4B,KAAK;CAExD,MAAM,CAAC,aAAa,kBAAkB,SAAS,MAAM;CACrD,MAAM,CAAC,cAAc,mBAAmB,SAAS,MAAM;CACvD,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAC7C,MAAM,CAAC,OAAO,YAAY,SAA4B,KAAA,EAAU;CAEhE,MAAM,EACJ,QACA,SACA,SACA,KACA,eACA,mBACA,sBACA,WACA,cACA,YACE;CAGJ,MAAM,aAAa,OAAO,QAAQ;AAClC,YAAW,UAAU;AAErB,iBAAgB;EAEd,MAAM,cAAc,gBAAgB,UAClC,QACA,UACC,QAAQ,WAAW,QAAQ,IAAI,EAChC;GACE;GACA;GACA;GACA;GACA,iBAAiB;AACf,mBAAe,KAAK;AACpB,oBAAgB,MAAM;AACtB,eAAW,MAAM;AACjB,aAAS,KAAA,EAAU;AACnB,iBAAa;;GAEf,oBAAoB;AAClB,mBAAe,MAAM;AACrB,oBAAgB,MAAM;AACtB,oBAAgB;;GAElB,UAAU,QAAQ;AAChB,eAAW,KAAK;AAChB,aAAS,IAAI;AACb,oBAAgB,MAAM;AACtB,cAAU,IAAI;;GAEjB,CACF;AAED,iBAAe,UAAU;EAGzB,MAAM,aAAa,gBAAgB,cAAc,QAAQ;AACzD,MAAI,YAAY;AACd,kBAAe,WAAW,YAAY;AACtC,mBAAgB,WAAW,aAAa;AACxC,cAAW,WAAW,QAAQ;AAC9B,YAAS,WAAW,MAAM;;AAI5B,eAAa;AACX,gBAAa;AACb,kBAAe,UAAU;;IAE1B,KAAK;AAIR,KAAI,CAFW,WAEJ,CAAC,WAAW,CACrB,QAAO;EACL,MAAM,OAAO,aAA8B;EAG3C,aAAa;EACb,cAAc;EACd,SAAS;EACT,OAAO,KAAA;EACP,iBAAiB;EAGjB,kBAAkB;EAGnB;AAGH,QAAO;EACL,MAAM,OAAO,YAA6B;AACxC,SAAM,gBAAgB,KAAK,QAAQ,SAAS,QAAQ;;EAEtD;EACA;EACA;EACA;EACA,iBAAiB;AACI,mBAAgB,cAAc,QACvC,EAAE,WAAW;;EAEzB,kBAAkB;AAChB,kBAAe,WAAW;;EAE7B"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["envSchema"],"sources":["../../src/redis/providers/RedisProvider.ts","../../src/redis/providers/BunRedisProvider.ts","../../src/redis/providers/RedisSubscriberProvider.ts","../../src/redis/providers/BunRedisSubscriberProvider.ts","../../src/redis/providers/NodeRedisProvider.ts","../../src/redis/providers/NodeRedisSubscriberProvider.ts","../../src/redis/index.ts"],"sourcesContent":["/**\n * Abstract Redis provider interface.\n *\n * This abstract class defines the common interface for Redis operations.\n * Implementations include:\n * - {@link NodeRedisProvider} - Uses `@redis/client` for Node.js runtime\n * - {@link BunRedisProvider} - Uses Bun's native `RedisClient` for Bun runtime\n *\n * @example\n * ```ts\n * // Inject the abstract provider - runtime selects the implementation\n * const redis = alepha.inject(RedisProvider);\n *\n * // Use common operations\n * await redis.set(\"key\", \"value\");\n * const value = await redis.get(\"key\");\n * ```\n */\nexport abstract class RedisProvider {\n /**\n * Whether the Redis client is ready to accept commands.\n */\n public abstract readonly isReady: boolean;\n\n /**\n * Connect to the Redis server.\n */\n public abstract connect(): Promise<void>;\n\n /**\n * Close the connection to the Redis server.\n */\n public abstract close(): Promise<void>;\n\n /**\n * Get the value of a key.\n *\n * @param key The key to get.\n * @returns The value as a Buffer, or undefined if the key does not exist.\n */\n public abstract get(key: string): Promise<Buffer | undefined>;\n\n /**\n * Set the value of a key.\n *\n * @param key The key to set.\n * @param value The value to set (Buffer or string).\n * @param options Optional set options (EX, PX, NX, XX, etc.).\n * @returns The value as a Buffer.\n */\n public abstract set(\n key: string,\n value: Buffer | string,\n options?: RedisSetOptions,\n ): Promise<Buffer>;\n\n /**\n * Check if a key exists.\n *\n * @param key The key to check.\n * @returns True if the key exists.\n */\n public abstract has(key: string): Promise<boolean>;\n\n /**\n * Get all keys matching a pattern.\n *\n * @param pattern The glob-style pattern to match.\n * @returns Array of matching key names.\n */\n public abstract keys(pattern: string): Promise<string[]>;\n\n /**\n * Delete one or more keys.\n *\n * @param keys The keys to delete.\n */\n public abstract del(keys: string[]): Promise<void>;\n\n // ---------------------------------------------------------\n // Queue operations (for alepha/queue-redis)\n // ---------------------------------------------------------\n\n /**\n * Push a value to the left (head) of a list.\n *\n * @param key The list key.\n * @param value The value to push.\n */\n public abstract lpush(key: string, value: string): Promise<void>;\n\n /**\n * Pop a value from the right (tail) of a list.\n *\n * @param key The list key.\n * @returns The value, or undefined if the list is empty.\n */\n public abstract rpop(key: string): Promise<string | undefined>;\n\n // ---------------------------------------------------------\n // Pub/Sub operations (for alepha/topic-redis)\n // ---------------------------------------------------------\n\n /**\n * Publish a message to a channel.\n *\n * @param channel The channel name.\n * @param message The message to publish.\n */\n public abstract publish(channel: string, message: string): Promise<void>;\n\n // ---------------------------------------------------------\n // Counter operations\n // ---------------------------------------------------------\n\n /**\n * Increment the integer value of a key by the given amount.\n *\n * If the key does not exist, it is set to 0 before performing the operation.\n * This operation is atomic.\n *\n * @param key The key to increment.\n * @param amount The amount to increment by.\n * @returns The new value after incrementing.\n */\n public abstract incr(key: string, amount: number): Promise<number>;\n}\n\n/**\n * Common Redis SET command options.\n * Compatible with @redis/client SetOptions format.\n */\nexport interface RedisSetOptions {\n /**\n * Set the specified expire time, in seconds.\n */\n EX?: number;\n /**\n * Set the specified expire time, in milliseconds.\n */\n PX?: number;\n /**\n * Set the specified Unix time at which the key will expire, in seconds.\n */\n EXAT?: number;\n /**\n * Set the specified Unix time at which the key will expire, in milliseconds.\n */\n PXAT?: number;\n /**\n * Only set the key if it does not already exist.\n */\n NX?: boolean;\n /**\n * Only set the key if it already exists.\n */\n XX?: boolean;\n /**\n * Retain the time to live associated with the key.\n */\n KEEPTTL?: boolean;\n /**\n * Return the old string stored at key, or nil if key did not exist.\n */\n GET?: boolean;\n /**\n * Alternative expiration format (compatible with @redis/client).\n */\n expiration?: {\n type: \"EX\" | \"PX\" | \"EXAT\" | \"PXAT\" | \"KEEPTTL\";\n value: number;\n };\n /**\n * Alternative condition format (compatible with @redis/client).\n */\n condition?: \"NX\" | \"XX\";\n}\n","import {\n $env,\n $hook,\n $inject,\n Alepha,\n AlephaError,\n type Static,\n t,\n} from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { RedisProvider, type RedisSetOptions } from \"./RedisProvider.ts\";\n\nconst envSchema = t.object({\n REDIS_URL: t.text({\n default: \"redis://localhost:6379\",\n description: \"Redis connection URL\",\n }),\n});\n\ndeclare module \"alepha\" {\n interface Env extends Partial<Static<typeof envSchema>> {}\n}\n\n/**\n * Bun Redis client provider using Bun's native Redis client.\n *\n * This provider uses Bun's built-in `RedisClient` class for Redis connections,\n * which provides excellent performance (7.9x faster than ioredis) on the Bun runtime.\n *\n * @example\n * ```ts\n * // Set REDIS_URL environment variable (default: redis://localhost:6379)\n * // REDIS_URL=redis://:password@myredis.example.com:6379\n *\n * // Or configure programmatically\n * alepha.with({\n * provide: RedisProvider,\n * use: BunRedisProvider,\n * });\n * ```\n */\nexport class BunRedisProvider extends RedisProvider {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly env = $env(envSchema);\n protected client?: Bun.RedisClient;\n\n public get publisher(): Bun.RedisClient {\n if (!this.client?.connected) {\n throw new AlephaError(\"Redis client is not ready\");\n }\n\n return this.client;\n }\n\n public override get isReady(): boolean {\n return this.client?.connected ?? false;\n }\n\n protected readonly start = $hook({\n on: \"start\",\n handler: () => this.connect(),\n });\n\n protected readonly stop = $hook({\n on: \"stop\",\n handler: () => this.close(),\n });\n\n /**\n * Connect to the Redis server.\n */\n public override async connect(): Promise<void> {\n // Check if we're running in Bun\n if (!this.alepha.isBun()) {\n throw new AlephaError(\n \"BunRedisProvider requires the Bun runtime. Use NodeRedisProvider for Node.js.\",\n );\n }\n\n this.log.debug(\"Connecting...\");\n\n this.client = new Bun.RedisClient(this.getUrl(), {\n autoReconnect: true,\n enableAutoPipelining: true,\n });\n\n this.client.onconnect = () => {\n this.log.trace(\"Redis connected\");\n };\n\n this.client.onclose = (error) => {\n if (this.alepha.isStarted() && error) {\n this.log.error(\"Redis connection closed\", error);\n }\n };\n\n await this.client.connect();\n\n this.log.info(\"Connection OK\");\n }\n\n /**\n * Close the connection to the Redis server.\n */\n public override async close(): Promise<void> {\n if (this.client) {\n this.log.debug(\"Closing connection...\");\n this.client.close();\n this.client = undefined;\n this.log.info(\"Connection closed\");\n }\n }\n\n /**\n * Create a duplicate connection for pub/sub or other isolated operations.\n */\n public async duplicate(): Promise<Bun.RedisClient> {\n if (typeof Bun === \"undefined\") {\n throw new AlephaError(\"BunRedisProvider requires the Bun runtime.\");\n }\n\n const client = new Bun.RedisClient(this.getUrl(), {\n autoReconnect: true,\n enableAutoPipelining: true,\n });\n\n client.onclose = (error) => {\n if (this.alepha.isStarted() && error) {\n this.log.error(\"Redis duplicate connection closed\", error);\n }\n };\n\n await client.connect();\n\n return client;\n }\n\n public override async get(key: string): Promise<Buffer | undefined> {\n this.log.trace(`Getting key ${key}`);\n const resp = await this.publisher.getBuffer(key);\n\n if (resp === null) {\n return undefined;\n }\n\n return Buffer.from(resp);\n }\n\n public override async set(\n key: string,\n value: Buffer | string,\n options?: RedisSetOptions,\n ): Promise<Buffer> {\n const buf = Buffer.isBuffer(value) ? value : Buffer.from(value, \"utf-8\");\n\n // Build SET command arguments\n const args: string[] = [key, buf.toString(\"binary\")];\n\n // Handle expiration object format (from alepha/cache-redis, alepha/lock-redis)\n if (options?.expiration) {\n if (options.expiration.type === \"KEEPTTL\") {\n args.push(\"KEEPTTL\");\n } else {\n args.push(options.expiration.type, String(options.expiration.value));\n }\n }\n\n // Handle direct expiration properties\n if (options?.EX !== undefined) {\n args.push(\"EX\", String(options.EX));\n }\n if (options?.PX !== undefined) {\n args.push(\"PX\", String(options.PX));\n }\n if (options?.EXAT !== undefined) {\n args.push(\"EXAT\", String(options.EXAT));\n }\n if (options?.PXAT !== undefined) {\n args.push(\"PXAT\", String(options.PXAT));\n }\n if (options?.KEEPTTL) {\n args.push(\"KEEPTTL\");\n }\n\n // Handle condition object format\n if (options?.condition === \"NX\") {\n args.push(\"NX\");\n } else if (options?.condition === \"XX\") {\n args.push(\"XX\");\n }\n\n // Handle direct condition properties\n if (options?.NX) {\n args.push(\"NX\");\n }\n if (options?.XX) {\n args.push(\"XX\");\n }\n if (options?.GET) {\n args.push(\"GET\");\n }\n\n if (args.length === 2) {\n // Simple set without options\n await this.publisher.set(key, buf);\n } else {\n // Set with options via raw command\n await this.publisher.send(\"SET\", args);\n }\n\n return buf;\n }\n\n public override async has(key: string): Promise<boolean> {\n return this.publisher.exists(key);\n }\n\n public override async keys(pattern: string): Promise<string[]> {\n const keys = await this.publisher.send(\"KEYS\", [pattern]);\n if (!Array.isArray(keys)) {\n return [];\n }\n return keys.map((key) =>\n key instanceof Uint8Array ? Buffer.from(key).toString() : String(key),\n );\n }\n\n public override async del(keys: string[]): Promise<void> {\n if (keys.length === 0) {\n return;\n }\n\n await this.publisher.send(\"DEL\", keys);\n }\n\n // ---------------------------------------------------------\n // Queue operations\n // ---------------------------------------------------------\n\n public override async lpush(key: string, value: string): Promise<void> {\n await this.publisher.send(\"LPUSH\", [key, value]);\n }\n\n public override async rpop(key: string): Promise<string | undefined> {\n const value = await this.publisher.send(\"RPOP\", [key]);\n if (value == null) {\n return undefined;\n }\n if (value instanceof Uint8Array) {\n return Buffer.from(value).toString();\n }\n return String(value);\n }\n\n // ---------------------------------------------------------\n // Pub/Sub operations\n // ---------------------------------------------------------\n\n public override async publish(\n channel: string,\n message: string,\n ): Promise<void> {\n await this.publisher.publish(channel, message);\n }\n\n // ---------------------------------------------------------\n // Counter operations\n // ---------------------------------------------------------\n\n public override async incr(key: string, amount: number): Promise<number> {\n const result = await this.publisher.send(\"INCRBY\", [key, String(amount)]);\n return Number(result);\n }\n\n /**\n * Get the Redis connection URL.\n */\n protected getUrl(): string {\n return this.env.REDIS_URL;\n }\n}\n","/**\n * Abstract Redis subscriber provider interface.\n *\n * This abstract class defines the common interface for Redis pub/sub subscriptions.\n * Implementations include:\n * - {@link NodeRedisSubscriberProvider} - Uses `@redis/client` for Node.js runtime\n * - {@link BunRedisSubscriberProvider} - Uses Bun's native `RedisClient` for Bun runtime\n *\n * Redis requires separate connections for pub/sub operations, so this provider\n * creates a dedicated connection for subscriptions.\n *\n * @example\n * ```ts\n * // Inject the abstract provider - runtime selects the implementation\n * const subscriber = alepha.inject(RedisSubscriberProvider);\n *\n * // Subscribe to a channel\n * await subscriber.subscribe(\"my-channel\", (message, channel) => {\n * console.log(`Received: ${message} on ${channel}`);\n * });\n * ```\n */\nexport abstract class RedisSubscriberProvider {\n /**\n * Whether the Redis subscriber client is ready to accept commands.\n */\n public abstract readonly isReady: boolean;\n\n /**\n * Connect to the Redis server for subscriptions.\n */\n public abstract connect(): Promise<void>;\n\n /**\n * Close the subscriber connection.\n */\n public abstract close(): Promise<void>;\n\n /**\n * Subscribe to a channel.\n *\n * @param channel The channel name.\n * @param callback The callback to invoke when a message is received.\n */\n public abstract subscribe(\n channel: string,\n callback: SubscribeCallback,\n ): Promise<void>;\n\n /**\n * Unsubscribe from a channel.\n *\n * @param channel The channel name.\n * @param callback Optional specific callback to remove.\n */\n public abstract unsubscribe(\n channel: string,\n callback?: SubscribeCallback,\n ): Promise<void>;\n}\n\n/**\n * Callback for subscription messages.\n */\nexport type SubscribeCallback = (message: string, channel: string) => void;\n","import { $hook, $inject, Alepha, AlephaError } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { BunRedisProvider } from \"./BunRedisProvider.ts\";\nimport {\n RedisSubscriberProvider,\n type SubscribeCallback,\n} from \"./RedisSubscriberProvider.ts\";\n\n/**\n * Bun Redis subscriber provider for pub/sub operations.\n *\n * This provider creates a dedicated Redis connection for subscriptions,\n * as Redis requires separate connections for pub/sub operations.\n *\n * @example\n * ```ts\n * const subscriber = alepha.inject(RedisSubscriberProvider);\n * await subscriber.subscribe(\"channel\", (message, channel) => {\n * console.log(`Received: ${message} on ${channel}`);\n * });\n * ```\n */\nexport class BunRedisSubscriberProvider extends RedisSubscriberProvider {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly redisProvider = $inject(BunRedisProvider);\n protected client?: Bun.RedisClient;\n\n public get subscriber(): Bun.RedisClient {\n if (!this.client?.connected) {\n throw new AlephaError(\"Redis subscriber client is not ready\");\n }\n\n return this.client;\n }\n\n public override get isReady(): boolean {\n return this.client?.connected ?? false;\n }\n\n protected readonly start = $hook({\n on: \"start\",\n handler: () => this.connect(),\n });\n\n protected readonly stop = $hook({\n on: \"stop\",\n handler: () => this.close(),\n });\n\n /**\n * Connect to the Redis server for subscriptions.\n */\n public override async connect(): Promise<void> {\n this.log.debug(\"Connecting subscriber...\");\n this.client = await this.redisProvider.duplicate();\n this.log.info(\"Subscriber connection OK\");\n }\n\n /**\n * Close the subscriber connection.\n */\n public override async close(): Promise<void> {\n if (this.client) {\n this.log.debug(\"Closing subscriber connection...\");\n this.client.close();\n this.client = undefined;\n this.log.info(\"Subscriber connection closed\");\n }\n }\n\n public override async subscribe(\n channel: string,\n callback: SubscribeCallback,\n ): Promise<void> {\n await this.subscriber.subscribe(channel, (message, ch) => {\n // Bun's callback provides Buffer or string, normalize to string\n const msg =\n typeof message === \"object\" && message !== null\n ? Buffer.from(message as Uint8Array).toString()\n : String(message);\n callback(msg, ch);\n });\n }\n\n public override async unsubscribe(\n channel: string,\n _callback?: SubscribeCallback,\n ): Promise<void> {\n // Bun's unsubscribe doesn't support callback filtering\n await this.subscriber.unsubscribe(channel);\n }\n}\n","import {\n createClient,\n RESP_TYPES,\n type RedisClientType,\n type SetOptions,\n} from \"@redis/client\";\nimport {\n $env,\n $hook,\n $inject,\n Alepha,\n AlephaError,\n type Static,\n t,\n} from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { RedisProvider, type RedisSetOptions } from \"./RedisProvider.ts\";\n\nconst envSchema = t.object({\n REDIS_URL: t.text({\n default: \"redis://localhost:6379\",\n description: \"Redis connection URL\",\n }),\n});\n\ndeclare module \"alepha\" {\n interface Env extends Partial<Static<typeof envSchema>> {}\n}\n\nexport type NodeRedisClient = RedisClientType<\n {},\n {},\n {},\n 3,\n { 36: BufferConstructor }\n>;\nexport type NodeRedisClientOptions = Parameters<typeof createClient>[0];\n\n/**\n * Node.js Redis client provider using `@redis/client`.\n *\n * This provider uses the official Redis client for Node.js runtime.\n *\n * @example\n * ```ts\n * // Set REDIS_URL environment variable (default: redis://localhost:6379)\n * // REDIS_URL=redis://:password@myredis.example.com:6379\n *\n * // Or configure programmatically\n * alepha.with({\n * provide: RedisProvider,\n * use: NodeRedisProvider,\n * });\n * ```\n */\nexport class NodeRedisProvider extends RedisProvider {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly env = $env(envSchema);\n protected readonly client = this.createClient();\n\n public get publisher(): NodeRedisClient {\n if (!this.client.isReady) {\n throw new AlephaError(\"Redis client is not ready\");\n }\n\n return this.client;\n }\n\n public override get isReady(): boolean {\n return this.client.isReady;\n }\n\n protected readonly start = $hook({\n on: \"start\",\n handler: () => this.connect(),\n });\n\n protected readonly stop = $hook({\n on: \"stop\",\n handler: () => this.close(),\n });\n\n /**\n * Connect to the Redis server.\n */\n public override async connect(): Promise<void> {\n this.log.debug(\"Connecting...\");\n await this.client.connect();\n this.log.info(\"Connection OK\");\n }\n\n /**\n * Close the connection to the Redis server.\n */\n public override async close(): Promise<void> {\n this.log.debug(\"Closing connection...\");\n await this.client.close();\n this.log.info(\"Connection closed\");\n }\n\n public duplicate(options?: Partial<NodeRedisClientOptions>): NodeRedisClient {\n return this.client\n .duplicate({\n ...options,\n RESP: 3,\n })\n .withTypeMapping({\n [RESP_TYPES.BLOB_STRING]: Buffer,\n });\n }\n\n public override async get(key: string): Promise<Buffer | undefined> {\n this.log.trace(`Getting key ${key}`);\n const resp = await this.publisher.get(key);\n\n if (resp === null) {\n return undefined;\n }\n\n return Buffer.from(resp);\n }\n\n public override async set(\n key: string,\n value: Buffer | string,\n options?: RedisSetOptions,\n ): Promise<Buffer> {\n const buf = Buffer.isBuffer(value) ? value : Buffer.from(value, \"utf-8\");\n\n // Convert RedisSetOptions to @redis/client SetOptions\n const setOptions: SetOptions = {};\n\n // Handle expiration object format (from alepha/cache-redis, alepha/lock-redis)\n if (options?.expiration) {\n if (options.expiration.type === \"KEEPTTL\") {\n setOptions.KEEPTTL = true;\n } else {\n setOptions[options.expiration.type] = options.expiration.value;\n }\n }\n\n // Handle direct expiration properties\n if (options?.EX !== undefined) {\n setOptions.EX = options.EX;\n }\n if (options?.PX !== undefined) {\n setOptions.PX = options.PX;\n }\n if (options?.EXAT !== undefined) {\n setOptions.EXAT = options.EXAT;\n }\n if (options?.PXAT !== undefined) {\n setOptions.PXAT = options.PXAT;\n }\n if (options?.KEEPTTL) {\n setOptions.KEEPTTL = true;\n }\n\n // Handle condition object format\n if (options?.condition === \"NX\") {\n setOptions.NX = true;\n } else if (options?.condition === \"XX\") {\n setOptions.XX = true;\n }\n\n // Handle direct condition properties\n if (options?.NX) {\n setOptions.NX = true;\n }\n if (options?.XX) {\n setOptions.XX = true;\n }\n if (options?.GET) {\n setOptions.GET = true;\n }\n\n const resp = await this.publisher.set(\n key,\n buf,\n Object.keys(setOptions).length > 0 ? setOptions : undefined,\n );\n\n if (resp === \"OK\" || !resp) {\n return buf;\n }\n\n return Buffer.from(resp);\n }\n\n public override async has(key: string): Promise<boolean> {\n const resp = await this.publisher.exists(key);\n return resp > 0;\n }\n\n public override async keys(pattern: string): Promise<string[]> {\n const keys = await this.publisher.keys(pattern);\n return keys.map((key) => key.toString());\n }\n\n public override async del(keys: string[]): Promise<void> {\n if (keys.length === 0) {\n return;\n }\n\n await this.publisher.del(keys);\n }\n\n // ---------------------------------------------------------\n // Queue operations\n // ---------------------------------------------------------\n\n public override async lpush(key: string, value: string): Promise<void> {\n await this.publisher.LPUSH(key, value);\n }\n\n public override async rpop(key: string): Promise<string | undefined> {\n const value = await this.publisher.RPOP(key);\n if (value == null) {\n return undefined;\n }\n return String(value);\n }\n\n // ---------------------------------------------------------\n // Pub/Sub operations\n // ---------------------------------------------------------\n\n public override async publish(\n channel: string,\n message: string,\n ): Promise<void> {\n await this.publisher.publish(channel, message);\n }\n\n // ---------------------------------------------------------\n // Counter operations\n // ---------------------------------------------------------\n\n public override async incr(key: string, amount: number): Promise<number> {\n return this.publisher.INCRBY(key, amount);\n }\n\n /**\n * Get the Redis connection URL.\n */\n protected getUrl(): string {\n return this.env.REDIS_URL;\n }\n\n /**\n * Redis client factory method.\n */\n protected createClient(): NodeRedisClient {\n const client = createClient({\n url: this.getUrl(),\n RESP: 3,\n }).withTypeMapping({\n [RESP_TYPES.BLOB_STRING]: Buffer,\n });\n\n client.on(\"error\", (error) => {\n if (this.alepha.isStarted()) {\n this.log.error(error);\n }\n });\n\n return client;\n }\n}\n","import { $hook, $inject, Alepha, AlephaError } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport {\n type NodeRedisClient,\n NodeRedisProvider,\n} from \"./NodeRedisProvider.ts\";\nimport {\n RedisSubscriberProvider,\n type SubscribeCallback,\n} from \"./RedisSubscriberProvider.ts\";\n\n/**\n * Node.js Redis subscriber provider using `@redis/client`.\n *\n * This provider creates a dedicated Redis connection for subscriptions,\n * as Redis requires separate connections for pub/sub operations.\n *\n * @example\n * ```ts\n * const subscriber = alepha.inject(RedisSubscriberProvider);\n * await subscriber.subscribe(\"channel\", (message, channel) => {\n * console.log(`Received: ${message} on ${channel}`);\n * });\n * ```\n */\nexport class NodeRedisSubscriberProvider extends RedisSubscriberProvider {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly redisProvider = $inject(NodeRedisProvider);\n protected readonly client: NodeRedisClient = this.createClient();\n\n public get subscriber(): NodeRedisClient {\n if (!this.client.isReady) {\n throw new AlephaError(\"Redis subscriber client is not ready\");\n }\n\n return this.client;\n }\n\n public override get isReady(): boolean {\n return this.client.isReady;\n }\n\n protected readonly start = $hook({\n on: \"start\",\n handler: () => this.connect(),\n });\n\n protected readonly stop = $hook({\n on: \"stop\",\n handler: () => this.close(),\n });\n\n public override async connect(): Promise<void> {\n this.log.debug(\"Connecting subscriber...\");\n await this.client.connect();\n this.log.info(\"Subscriber connection OK\");\n }\n\n public override async close(): Promise<void> {\n if (!this.client.isReady) {\n this.log.debug(\"Subscriber client not ready, skipping close\");\n return;\n }\n this.log.debug(\"Closing subscriber connection...\");\n await this.client.close();\n this.log.info(\"Subscriber connection closed\");\n }\n\n public override async subscribe(\n channel: string,\n callback: SubscribeCallback,\n ): Promise<void> {\n await this.subscriber.subscribe(channel, callback);\n }\n\n public override async unsubscribe(\n channel: string,\n callback?: SubscribeCallback,\n ): Promise<void> {\n await this.subscriber.unsubscribe(channel, callback);\n }\n\n /**\n * Redis subscriber client factory method.\n */\n protected createClient(): NodeRedisClient {\n const client = this.redisProvider.duplicate();\n\n client.on(\"error\", (error) => {\n if (this.alepha.isStarted()) {\n this.log.error(error);\n }\n });\n\n return client;\n }\n}\n","import { $module, type Alepha } from \"alepha\";\nimport { BunRedisProvider } from \"./providers/BunRedisProvider.ts\";\nimport { BunRedisSubscriberProvider } from \"./providers/BunRedisSubscriberProvider.ts\";\nimport { NodeRedisProvider } from \"./providers/NodeRedisProvider.ts\";\nimport { NodeRedisSubscriberProvider } from \"./providers/NodeRedisSubscriberProvider.ts\";\nimport { RedisProvider } from \"./providers/RedisProvider.ts\";\nimport { RedisSubscriberProvider } from \"./providers/RedisSubscriberProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./providers/BunRedisProvider.ts\";\nexport * from \"./providers/BunRedisSubscriberProvider.ts\";\nexport * from \"./providers/NodeRedisProvider.ts\";\nexport * from \"./providers/NodeRedisSubscriberProvider.ts\";\nexport * from \"./providers/RedisProvider.ts\";\nexport * from \"./providers/RedisSubscriberProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Redis client wrapper.\n *\n * **Features:**\n * - Connection pooling\n * - Automatic reconnection\n * - Command pipelining\n * - Pub/sub support\n *\n * @module alepha.redis\n */\nexport const AlephaRedis = $module({\n name: \"alepha.redis\",\n services: [RedisProvider, RedisSubscriberProvider],\n variants: [\n NodeRedisProvider,\n NodeRedisSubscriberProvider,\n BunRedisProvider,\n BunRedisSubscriberProvider,\n ],\n register: (alepha: Alepha) => {\n if (alepha.isBun()) {\n alepha\n .with({\n provide: RedisProvider,\n use: BunRedisProvider,\n })\n .with({\n provide: RedisSubscriberProvider,\n use: BunRedisSubscriberProvider,\n });\n } else {\n alepha\n .with({\n provide: RedisProvider,\n use: NodeRedisProvider,\n })\n .with({\n provide: RedisSubscriberProvider,\n use: NodeRedisSubscriberProvider,\n });\n }\n },\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["envSchema"],"sources":["../../src/redis/providers/RedisProvider.ts","../../src/redis/providers/BunRedisProvider.ts","../../src/redis/providers/RedisSubscriberProvider.ts","../../src/redis/providers/BunRedisSubscriberProvider.ts","../../src/redis/providers/NodeRedisProvider.ts","../../src/redis/providers/NodeRedisSubscriberProvider.ts","../../src/redis/index.ts"],"sourcesContent":["/**\n * Abstract Redis provider interface.\n *\n * This abstract class defines the common interface for Redis operations.\n * Implementations include:\n * - {@link NodeRedisProvider} - Uses `@redis/client` for Node.js runtime\n * - {@link BunRedisProvider} - Uses Bun's native `RedisClient` for Bun runtime\n *\n * @example\n * ```ts\n * // Inject the abstract provider - runtime selects the implementation\n * const redis = alepha.inject(RedisProvider);\n *\n * // Use common operations\n * await redis.set(\"key\", \"value\");\n * const value = await redis.get(\"key\");\n * ```\n */\nexport abstract class RedisProvider {\n /**\n * Whether the Redis client is ready to accept commands.\n */\n public abstract readonly isReady: boolean;\n\n /**\n * Connect to the Redis server.\n */\n public abstract connect(): Promise<void>;\n\n /**\n * Close the connection to the Redis server.\n */\n public abstract close(): Promise<void>;\n\n /**\n * Get the value of a key.\n *\n * @param key The key to get.\n * @returns The value as a Buffer, or undefined if the key does not exist.\n */\n public abstract get(key: string): Promise<Buffer | undefined>;\n\n /**\n * Set the value of a key.\n *\n * @param key The key to set.\n * @param value The value to set (Buffer or string).\n * @param options Optional set options (EX, PX, NX, XX, etc.).\n * @returns The value as a Buffer.\n */\n public abstract set(\n key: string,\n value: Buffer | string,\n options?: RedisSetOptions,\n ): Promise<Buffer>;\n\n /**\n * Check if a key exists.\n *\n * @param key The key to check.\n * @returns True if the key exists.\n */\n public abstract has(key: string): Promise<boolean>;\n\n /**\n * Get all keys matching a pattern.\n *\n * @param pattern The glob-style pattern to match.\n * @returns Array of matching key names.\n */\n public abstract keys(pattern: string): Promise<string[]>;\n\n /**\n * Delete one or more keys.\n *\n * @param keys The keys to delete.\n */\n public abstract del(keys: string[]): Promise<void>;\n\n // ---------------------------------------------------------\n // Queue operations (for alepha/queue-redis)\n // ---------------------------------------------------------\n\n /**\n * Push a value to the left (head) of a list.\n *\n * @param key The list key.\n * @param value The value to push.\n */\n public abstract lpush(key: string, value: string): Promise<void>;\n\n /**\n * Pop a value from the right (tail) of a list.\n *\n * @param key The list key.\n * @returns The value, or undefined if the list is empty.\n */\n public abstract rpop(key: string): Promise<string | undefined>;\n\n // ---------------------------------------------------------\n // Pub/Sub operations (for alepha/topic-redis)\n // ---------------------------------------------------------\n\n /**\n * Publish a message to a channel.\n *\n * @param channel The channel name.\n * @param message The message to publish.\n */\n public abstract publish(channel: string, message: string): Promise<void>;\n\n // ---------------------------------------------------------\n // Counter operations\n // ---------------------------------------------------------\n\n /**\n * Increment the integer value of a key by the given amount.\n *\n * If the key does not exist, it is set to 0 before performing the operation.\n * This operation is atomic.\n *\n * @param key The key to increment.\n * @param amount The amount to increment by.\n * @returns The new value after incrementing.\n */\n public abstract incr(key: string, amount: number): Promise<number>;\n}\n\n/**\n * Common Redis SET command options.\n * Compatible with @redis/client SetOptions format.\n */\nexport interface RedisSetOptions {\n /**\n * Set the specified expire time, in seconds.\n */\n EX?: number;\n /**\n * Set the specified expire time, in milliseconds.\n */\n PX?: number;\n /**\n * Set the specified Unix time at which the key will expire, in seconds.\n */\n EXAT?: number;\n /**\n * Set the specified Unix time at which the key will expire, in milliseconds.\n */\n PXAT?: number;\n /**\n * Only set the key if it does not already exist.\n */\n NX?: boolean;\n /**\n * Only set the key if it already exists.\n */\n XX?: boolean;\n /**\n * Retain the time to live associated with the key.\n */\n KEEPTTL?: boolean;\n /**\n * Return the old string stored at key, or nil if key did not exist.\n */\n GET?: boolean;\n /**\n * Alternative expiration format (compatible with @redis/client).\n */\n expiration?: {\n type: \"EX\" | \"PX\" | \"EXAT\" | \"PXAT\" | \"KEEPTTL\";\n value: number;\n };\n /**\n * Alternative condition format (compatible with @redis/client).\n */\n condition?: \"NX\" | \"XX\";\n}\n","import {\n $env,\n $hook,\n $inject,\n Alepha,\n AlephaError,\n type Static,\n t,\n} from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { RedisProvider, type RedisSetOptions } from \"./RedisProvider.ts\";\n\nconst envSchema = t.object({\n REDIS_URL: t.text({\n default: \"redis://localhost:6379\",\n description: \"Redis connection URL\",\n }),\n});\n\ndeclare module \"alepha\" {\n interface Env extends Partial<Static<typeof envSchema>> {}\n}\n\n/**\n * Bun Redis client provider using Bun's native Redis client.\n *\n * This provider uses Bun's built-in `RedisClient` class for Redis connections,\n * which provides excellent performance (7.9x faster than ioredis) on the Bun runtime.\n *\n * @example\n * ```ts\n * // Set REDIS_URL environment variable (default: redis://localhost:6379)\n * // REDIS_URL=redis://:password@myredis.example.com:6379\n *\n * // Or configure programmatically\n * alepha.with({\n * provide: RedisProvider,\n * use: BunRedisProvider,\n * });\n * ```\n */\nexport class BunRedisProvider extends RedisProvider {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly env = $env(envSchema);\n protected client?: Bun.RedisClient;\n\n public get publisher(): Bun.RedisClient {\n if (!this.client?.connected) {\n throw new AlephaError(\"Redis client is not ready\");\n }\n\n return this.client;\n }\n\n public override get isReady(): boolean {\n return this.client?.connected ?? false;\n }\n\n protected readonly start = $hook({\n on: \"start\",\n handler: () => this.connect(),\n });\n\n protected readonly stop = $hook({\n on: \"stop\",\n handler: () => this.close(),\n });\n\n /**\n * Connect to the Redis server.\n */\n public override async connect(): Promise<void> {\n // Check if we're running in Bun\n if (!this.alepha.isBun()) {\n throw new AlephaError(\n \"BunRedisProvider requires the Bun runtime. Use NodeRedisProvider for Node.js.\",\n );\n }\n\n this.log.debug(\"Connecting...\");\n\n this.client = new Bun.RedisClient(this.getUrl(), {\n autoReconnect: true,\n enableAutoPipelining: true,\n });\n\n this.client.onconnect = () => {\n this.log.trace(\"Redis connected\");\n };\n\n this.client.onclose = (error) => {\n if (this.alepha.isStarted() && error) {\n this.log.error(\"Redis connection closed\", error);\n }\n };\n\n await this.client.connect();\n\n this.log.info(\"Connection OK\");\n }\n\n /**\n * Close the connection to the Redis server.\n */\n public override async close(): Promise<void> {\n if (this.client) {\n this.log.debug(\"Closing connection...\");\n this.client.close();\n this.client = undefined;\n this.log.info(\"Connection closed\");\n }\n }\n\n /**\n * Create a duplicate connection for pub/sub or other isolated operations.\n */\n public async duplicate(): Promise<Bun.RedisClient> {\n if (typeof Bun === \"undefined\") {\n throw new AlephaError(\"BunRedisProvider requires the Bun runtime.\");\n }\n\n const client = new Bun.RedisClient(this.getUrl(), {\n autoReconnect: true,\n enableAutoPipelining: true,\n });\n\n client.onclose = (error) => {\n if (this.alepha.isStarted() && error) {\n this.log.error(\"Redis duplicate connection closed\", error);\n }\n };\n\n await client.connect();\n\n return client;\n }\n\n public override async get(key: string): Promise<Buffer | undefined> {\n this.log.trace(`Getting key ${key}`);\n const resp = await this.publisher.getBuffer(key);\n\n if (resp === null) {\n return undefined;\n }\n\n return Buffer.from(resp);\n }\n\n public override async set(\n key: string,\n value: Buffer | string,\n options?: RedisSetOptions,\n ): Promise<Buffer> {\n const buf = Buffer.isBuffer(value) ? value : Buffer.from(value, \"utf-8\");\n\n // Build SET command arguments\n const args: string[] = [key, buf.toString(\"binary\")];\n\n // Handle expiration object format (from alepha/cache-redis, alepha/lock-redis)\n if (options?.expiration) {\n if (options.expiration.type === \"KEEPTTL\") {\n args.push(\"KEEPTTL\");\n } else {\n args.push(options.expiration.type, String(options.expiration.value));\n }\n }\n\n // Handle direct expiration properties\n if (options?.EX !== undefined) {\n args.push(\"EX\", String(options.EX));\n }\n if (options?.PX !== undefined) {\n args.push(\"PX\", String(options.PX));\n }\n if (options?.EXAT !== undefined) {\n args.push(\"EXAT\", String(options.EXAT));\n }\n if (options?.PXAT !== undefined) {\n args.push(\"PXAT\", String(options.PXAT));\n }\n if (options?.KEEPTTL) {\n args.push(\"KEEPTTL\");\n }\n\n // Handle condition object format\n if (options?.condition === \"NX\") {\n args.push(\"NX\");\n } else if (options?.condition === \"XX\") {\n args.push(\"XX\");\n }\n\n // Handle direct condition properties\n if (options?.NX) {\n args.push(\"NX\");\n }\n if (options?.XX) {\n args.push(\"XX\");\n }\n if (options?.GET) {\n args.push(\"GET\");\n }\n\n if (args.length === 2) {\n // Simple set without options\n await this.publisher.set(key, buf);\n } else {\n // Set with options via raw command\n await this.publisher.send(\"SET\", args);\n }\n\n return buf;\n }\n\n public override async has(key: string): Promise<boolean> {\n return this.publisher.exists(key);\n }\n\n public override async keys(pattern: string): Promise<string[]> {\n const keys = await this.publisher.send(\"KEYS\", [pattern]);\n if (!Array.isArray(keys)) {\n return [];\n }\n return keys.map((key) =>\n key instanceof Uint8Array ? Buffer.from(key).toString() : String(key),\n );\n }\n\n public override async del(keys: string[]): Promise<void> {\n if (keys.length === 0) {\n return;\n }\n\n await this.publisher.send(\"DEL\", keys);\n }\n\n // ---------------------------------------------------------\n // Queue operations\n // ---------------------------------------------------------\n\n public override async lpush(key: string, value: string): Promise<void> {\n await this.publisher.send(\"LPUSH\", [key, value]);\n }\n\n public override async rpop(key: string): Promise<string | undefined> {\n const value = await this.publisher.send(\"RPOP\", [key]);\n if (value == null) {\n return undefined;\n }\n if (value instanceof Uint8Array) {\n return Buffer.from(value).toString();\n }\n return String(value);\n }\n\n // ---------------------------------------------------------\n // Pub/Sub operations\n // ---------------------------------------------------------\n\n public override async publish(\n channel: string,\n message: string,\n ): Promise<void> {\n await this.publisher.publish(channel, message);\n }\n\n // ---------------------------------------------------------\n // Counter operations\n // ---------------------------------------------------------\n\n public override async incr(key: string, amount: number): Promise<number> {\n const result = await this.publisher.send(\"INCRBY\", [key, String(amount)]);\n return Number(result);\n }\n\n /**\n * Get the Redis connection URL.\n */\n protected getUrl(): string {\n return this.env.REDIS_URL;\n }\n}\n","/**\n * Abstract Redis subscriber provider interface.\n *\n * This abstract class defines the common interface for Redis pub/sub subscriptions.\n * Implementations include:\n * - {@link NodeRedisSubscriberProvider} - Uses `@redis/client` for Node.js runtime\n * - {@link BunRedisSubscriberProvider} - Uses Bun's native `RedisClient` for Bun runtime\n *\n * Redis requires separate connections for pub/sub operations, so this provider\n * creates a dedicated connection for subscriptions.\n *\n * @example\n * ```ts\n * // Inject the abstract provider - runtime selects the implementation\n * const subscriber = alepha.inject(RedisSubscriberProvider);\n *\n * // Subscribe to a channel\n * await subscriber.subscribe(\"my-channel\", (message, channel) => {\n * console.log(`Received: ${message} on ${channel}`);\n * });\n * ```\n */\nexport abstract class RedisSubscriberProvider {\n /**\n * Whether the Redis subscriber client is ready to accept commands.\n */\n public abstract readonly isReady: boolean;\n\n /**\n * Connect to the Redis server for subscriptions.\n */\n public abstract connect(): Promise<void>;\n\n /**\n * Close the subscriber connection.\n */\n public abstract close(): Promise<void>;\n\n /**\n * Subscribe to a channel.\n *\n * @param channel The channel name.\n * @param callback The callback to invoke when a message is received.\n */\n public abstract subscribe(\n channel: string,\n callback: SubscribeCallback,\n ): Promise<void>;\n\n /**\n * Unsubscribe from a channel.\n *\n * @param channel The channel name.\n * @param callback Optional specific callback to remove.\n */\n public abstract unsubscribe(\n channel: string,\n callback?: SubscribeCallback,\n ): Promise<void>;\n}\n\n/**\n * Callback for subscription messages.\n */\nexport type SubscribeCallback = (message: string, channel: string) => void;\n","import { $hook, $inject, Alepha, AlephaError } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { BunRedisProvider } from \"./BunRedisProvider.ts\";\nimport {\n RedisSubscriberProvider,\n type SubscribeCallback,\n} from \"./RedisSubscriberProvider.ts\";\n\n/**\n * Bun Redis subscriber provider for pub/sub operations.\n *\n * This provider creates a dedicated Redis connection for subscriptions,\n * as Redis requires separate connections for pub/sub operations.\n *\n * @example\n * ```ts\n * const subscriber = alepha.inject(RedisSubscriberProvider);\n * await subscriber.subscribe(\"channel\", (message, channel) => {\n * console.log(`Received: ${message} on ${channel}`);\n * });\n * ```\n */\nexport class BunRedisSubscriberProvider extends RedisSubscriberProvider {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly redisProvider = $inject(BunRedisProvider);\n protected client?: Bun.RedisClient;\n\n public get subscriber(): Bun.RedisClient {\n if (!this.client?.connected) {\n throw new AlephaError(\"Redis subscriber client is not ready\");\n }\n\n return this.client;\n }\n\n public override get isReady(): boolean {\n return this.client?.connected ?? false;\n }\n\n protected readonly start = $hook({\n on: \"start\",\n handler: () => this.connect(),\n });\n\n protected readonly stop = $hook({\n on: \"stop\",\n handler: () => this.close(),\n });\n\n /**\n * Connect to the Redis server for subscriptions.\n */\n public override async connect(): Promise<void> {\n this.log.debug(\"Connecting subscriber...\");\n this.client = await this.redisProvider.duplicate();\n this.log.info(\"Subscriber connection OK\");\n }\n\n /**\n * Close the subscriber connection.\n */\n public override async close(): Promise<void> {\n if (this.client) {\n this.log.debug(\"Closing subscriber connection...\");\n this.client.close();\n this.client = undefined;\n this.log.info(\"Subscriber connection closed\");\n }\n }\n\n public override async subscribe(\n channel: string,\n callback: SubscribeCallback,\n ): Promise<void> {\n await this.subscriber.subscribe(channel, (message, ch) => {\n // Bun's callback provides Buffer or string, normalize to string\n const msg =\n typeof message === \"object\" && message !== null\n ? Buffer.from(message as Uint8Array).toString()\n : String(message);\n callback(msg, ch);\n });\n }\n\n public override async unsubscribe(\n channel: string,\n _callback?: SubscribeCallback,\n ): Promise<void> {\n // Bun's unsubscribe doesn't support callback filtering\n await this.subscriber.unsubscribe(channel);\n }\n}\n","import {\n createClient,\n RESP_TYPES,\n type RedisClientType,\n type SetOptions,\n} from \"@redis/client\";\nimport {\n $env,\n $hook,\n $inject,\n Alepha,\n AlephaError,\n type Static,\n t,\n} from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { RedisProvider, type RedisSetOptions } from \"./RedisProvider.ts\";\n\nconst envSchema = t.object({\n REDIS_URL: t.text({\n default: \"redis://localhost:6379\",\n description: \"Redis connection URL\",\n }),\n});\n\ndeclare module \"alepha\" {\n interface Env extends Partial<Static<typeof envSchema>> {}\n}\n\nexport type NodeRedisClient = RedisClientType<\n {},\n {},\n {},\n 3,\n { 36: BufferConstructor }\n>;\nexport type NodeRedisClientOptions = Parameters<typeof createClient>[0];\n\n/**\n * Node.js Redis client provider using `@redis/client`.\n *\n * This provider uses the official Redis client for Node.js runtime.\n *\n * @example\n * ```ts\n * // Set REDIS_URL environment variable (default: redis://localhost:6379)\n * // REDIS_URL=redis://:password@myredis.example.com:6379\n *\n * // Or configure programmatically\n * alepha.with({\n * provide: RedisProvider,\n * use: NodeRedisProvider,\n * });\n * ```\n */\nexport class NodeRedisProvider extends RedisProvider {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly env = $env(envSchema);\n protected readonly client = this.createClient();\n\n public get publisher(): NodeRedisClient {\n if (!this.client.isReady) {\n throw new AlephaError(\"Redis client is not ready\");\n }\n\n return this.client;\n }\n\n public override get isReady(): boolean {\n return this.client.isReady;\n }\n\n protected readonly start = $hook({\n on: \"start\",\n handler: () => this.connect(),\n });\n\n protected readonly stop = $hook({\n on: \"stop\",\n handler: () => this.close(),\n });\n\n /**\n * Connect to the Redis server.\n */\n public override async connect(): Promise<void> {\n this.log.debug(\"Connecting...\");\n await this.client.connect();\n this.log.info(\"Connection OK\");\n }\n\n /**\n * Close the connection to the Redis server.\n */\n public override async close(): Promise<void> {\n this.log.debug(\"Closing connection...\");\n await this.client.close();\n this.log.info(\"Connection closed\");\n }\n\n public duplicate(options?: Partial<NodeRedisClientOptions>): NodeRedisClient {\n return this.client\n .duplicate({\n ...options,\n RESP: 3,\n })\n .withTypeMapping({\n [RESP_TYPES.BLOB_STRING]: Buffer,\n });\n }\n\n public override async get(key: string): Promise<Buffer | undefined> {\n this.log.trace(`Getting key ${key}`);\n const resp = await this.publisher.get(key);\n\n if (resp === null) {\n return undefined;\n }\n\n return Buffer.from(resp);\n }\n\n public override async set(\n key: string,\n value: Buffer | string,\n options?: RedisSetOptions,\n ): Promise<Buffer> {\n const buf = Buffer.isBuffer(value) ? value : Buffer.from(value, \"utf-8\");\n\n // Convert RedisSetOptions to @redis/client SetOptions\n const setOptions: SetOptions = {};\n\n // Handle expiration object format (from alepha/cache-redis, alepha/lock-redis)\n if (options?.expiration) {\n if (options.expiration.type === \"KEEPTTL\") {\n setOptions.KEEPTTL = true;\n } else {\n setOptions[options.expiration.type] = options.expiration.value;\n }\n }\n\n // Handle direct expiration properties\n if (options?.EX !== undefined) {\n setOptions.EX = options.EX;\n }\n if (options?.PX !== undefined) {\n setOptions.PX = options.PX;\n }\n if (options?.EXAT !== undefined) {\n setOptions.EXAT = options.EXAT;\n }\n if (options?.PXAT !== undefined) {\n setOptions.PXAT = options.PXAT;\n }\n if (options?.KEEPTTL) {\n setOptions.KEEPTTL = true;\n }\n\n // Handle condition object format\n if (options?.condition === \"NX\") {\n setOptions.NX = true;\n } else if (options?.condition === \"XX\") {\n setOptions.XX = true;\n }\n\n // Handle direct condition properties\n if (options?.NX) {\n setOptions.NX = true;\n }\n if (options?.XX) {\n setOptions.XX = true;\n }\n if (options?.GET) {\n setOptions.GET = true;\n }\n\n const resp = await this.publisher.set(\n key,\n buf,\n Object.keys(setOptions).length > 0 ? setOptions : undefined,\n );\n\n if (resp === \"OK\" || !resp) {\n return buf;\n }\n\n return Buffer.from(resp);\n }\n\n public override async has(key: string): Promise<boolean> {\n const resp = await this.publisher.exists(key);\n return resp > 0;\n }\n\n public override async keys(pattern: string): Promise<string[]> {\n const keys = await this.publisher.keys(pattern);\n return keys.map((key) => key.toString());\n }\n\n public override async del(keys: string[]): Promise<void> {\n if (keys.length === 0) {\n return;\n }\n\n await this.publisher.del(keys);\n }\n\n // ---------------------------------------------------------\n // Queue operations\n // ---------------------------------------------------------\n\n public override async lpush(key: string, value: string): Promise<void> {\n await this.publisher.LPUSH(key, value);\n }\n\n public override async rpop(key: string): Promise<string | undefined> {\n const value = await this.publisher.RPOP(key);\n if (value == null) {\n return undefined;\n }\n return String(value);\n }\n\n // ---------------------------------------------------------\n // Pub/Sub operations\n // ---------------------------------------------------------\n\n public override async publish(\n channel: string,\n message: string,\n ): Promise<void> {\n await this.publisher.publish(channel, message);\n }\n\n // ---------------------------------------------------------\n // Counter operations\n // ---------------------------------------------------------\n\n public override async incr(key: string, amount: number): Promise<number> {\n return this.publisher.INCRBY(key, amount);\n }\n\n /**\n * Get the Redis connection URL.\n */\n protected getUrl(): string {\n return this.env.REDIS_URL;\n }\n\n /**\n * Redis client factory method.\n */\n protected createClient(): NodeRedisClient {\n const client = createClient({\n url: this.getUrl(),\n RESP: 3,\n }).withTypeMapping({\n [RESP_TYPES.BLOB_STRING]: Buffer,\n });\n\n client.on(\"error\", (error) => {\n if (this.alepha.isStarted()) {\n this.log.error(error);\n }\n });\n\n return client;\n }\n}\n","import { $hook, $inject, Alepha, AlephaError } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport {\n type NodeRedisClient,\n NodeRedisProvider,\n} from \"./NodeRedisProvider.ts\";\nimport {\n RedisSubscriberProvider,\n type SubscribeCallback,\n} from \"./RedisSubscriberProvider.ts\";\n\n/**\n * Node.js Redis subscriber provider using `@redis/client`.\n *\n * This provider creates a dedicated Redis connection for subscriptions,\n * as Redis requires separate connections for pub/sub operations.\n *\n * @example\n * ```ts\n * const subscriber = alepha.inject(RedisSubscriberProvider);\n * await subscriber.subscribe(\"channel\", (message, channel) => {\n * console.log(`Received: ${message} on ${channel}`);\n * });\n * ```\n */\nexport class NodeRedisSubscriberProvider extends RedisSubscriberProvider {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly redisProvider = $inject(NodeRedisProvider);\n protected readonly client: NodeRedisClient = this.createClient();\n\n public get subscriber(): NodeRedisClient {\n if (!this.client.isReady) {\n throw new AlephaError(\"Redis subscriber client is not ready\");\n }\n\n return this.client;\n }\n\n public override get isReady(): boolean {\n return this.client.isReady;\n }\n\n protected readonly start = $hook({\n on: \"start\",\n handler: () => this.connect(),\n });\n\n protected readonly stop = $hook({\n on: \"stop\",\n handler: () => this.close(),\n });\n\n public override async connect(): Promise<void> {\n this.log.debug(\"Connecting subscriber...\");\n await this.client.connect();\n this.log.info(\"Subscriber connection OK\");\n }\n\n public override async close(): Promise<void> {\n if (!this.client.isReady) {\n this.log.debug(\"Subscriber client not ready, skipping close\");\n return;\n }\n this.log.debug(\"Closing subscriber connection...\");\n await this.client.close();\n this.log.info(\"Subscriber connection closed\");\n }\n\n public override async subscribe(\n channel: string,\n callback: SubscribeCallback,\n ): Promise<void> {\n await this.subscriber.subscribe(channel, callback);\n }\n\n public override async unsubscribe(\n channel: string,\n callback?: SubscribeCallback,\n ): Promise<void> {\n await this.subscriber.unsubscribe(channel, callback);\n }\n\n /**\n * Redis subscriber client factory method.\n */\n protected createClient(): NodeRedisClient {\n const client = this.redisProvider.duplicate();\n\n client.on(\"error\", (error) => {\n if (this.alepha.isStarted()) {\n this.log.error(error);\n }\n });\n\n return client;\n }\n}\n","import { $module, type Alepha } from \"alepha\";\nimport { BunRedisProvider } from \"./providers/BunRedisProvider.ts\";\nimport { BunRedisSubscriberProvider } from \"./providers/BunRedisSubscriberProvider.ts\";\nimport { NodeRedisProvider } from \"./providers/NodeRedisProvider.ts\";\nimport { NodeRedisSubscriberProvider } from \"./providers/NodeRedisSubscriberProvider.ts\";\nimport { RedisProvider } from \"./providers/RedisProvider.ts\";\nimport { RedisSubscriberProvider } from \"./providers/RedisSubscriberProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./providers/BunRedisProvider.ts\";\nexport * from \"./providers/BunRedisSubscriberProvider.ts\";\nexport * from \"./providers/NodeRedisProvider.ts\";\nexport * from \"./providers/NodeRedisSubscriberProvider.ts\";\nexport * from \"./providers/RedisProvider.ts\";\nexport * from \"./providers/RedisSubscriberProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Redis client wrapper.\n *\n * **Features:**\n * - Connection pooling\n * - Automatic reconnection\n * - Command pipelining\n * - Pub/sub support\n *\n * @module alepha.redis\n */\nexport const AlephaRedis = $module({\n name: \"alepha.redis\",\n services: [RedisProvider, RedisSubscriberProvider],\n variants: [\n NodeRedisProvider,\n NodeRedisSubscriberProvider,\n BunRedisProvider,\n BunRedisSubscriberProvider,\n ],\n register: (alepha: Alepha) => {\n if (alepha.isBun()) {\n alepha\n .with({\n provide: RedisProvider,\n use: BunRedisProvider,\n })\n .with({\n provide: RedisSubscriberProvider,\n use: BunRedisSubscriberProvider,\n });\n } else {\n alepha\n .with({\n provide: RedisProvider,\n use: NodeRedisProvider,\n })\n .with({\n provide: RedisSubscriberProvider,\n use: NodeRedisSubscriberProvider,\n });\n }\n },\n});\n"],"mappings":"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"}
@@ -2,6 +2,7 @@ import * as _$alepha from "alepha";
2
2
  import { Alepha, Async, KIND, Primitive, Static } from "alepha";
3
3
  import { DateTime, DateTimeProvider, DurationLike } from "alepha/datetime";
4
4
  import * as _$alepha_logger0 from "alepha/logger";
5
+ import { Cron } from "cron-schedule";
5
6
  import * as _$dayjs from "dayjs";
6
7
 
7
8
  //#region ../../src/scheduler/constants/CRON.d.ts
@@ -14,89 +15,6 @@ declare const CRON: {
14
15
  EVERY_DAY_AT_MIDNIGHT: string;
15
16
  };
16
17
  //#endregion
17
- //#region ../../../../node_modules/cron-schedule/dist/cron.d.ts
18
- /**
19
- * An object with contains for each element of a date, which values are allowed.
20
- * Everything starting at 0, except for days.
21
- */
22
- interface ICronDefinition {
23
- readonly seconds: Set<number>;
24
- readonly minutes: Set<number>;
25
- readonly hours: Set<number>;
26
- readonly days: Set<number>;
27
- readonly months: Set<number>;
28
- readonly weekdays: Set<number>;
29
- }
30
- declare class Cron {
31
- readonly seconds: ReadonlyArray<number>;
32
- readonly minutes: ReadonlyArray<number>;
33
- readonly hours: ReadonlyArray<number>;
34
- readonly days: ReadonlyArray<number>;
35
- readonly months: ReadonlyArray<number>;
36
- readonly weekdays: ReadonlyArray<number>;
37
- readonly reversed: {
38
- seconds: ReadonlyArray<number>;
39
- minutes: ReadonlyArray<number>;
40
- hours: ReadonlyArray<number>;
41
- days: ReadonlyArray<number>;
42
- months: ReadonlyArray<number>;
43
- weekdays: ReadonlyArray<number>;
44
- };
45
- constructor({
46
- seconds,
47
- minutes,
48
- hours,
49
- days,
50
- months,
51
- weekdays
52
- }: ICronDefinition);
53
- /**
54
- * Find the next or previous hour, starting from the given start hour that matches the hour constraint.
55
- * startHour itself might also be allowed.
56
- */
57
- private findAllowedHour;
58
- /**
59
- * Find the next or previous minute, starting from the given start minute that matches the minute constraint.
60
- * startMinute itself might also be allowed.
61
- */
62
- private findAllowedMinute;
63
- /**
64
- * Find the next or previous second, starting from the given start second that matches the second constraint.
65
- * startSecond itself IS NOT allowed.
66
- */
67
- private findAllowedSecond;
68
- /**
69
- * Find the next or previous time, starting from the given start time that matches the hour, minute
70
- * and second constraints. startTime itself might also be allowed.
71
- */
72
- private findAllowedTime;
73
- /**
74
- * Find the next or previous day in the given month, starting from the given startDay
75
- * that matches either the day or the weekday constraint. startDay itself might also be allowed.
76
- */
77
- private findAllowedDayInMonth;
78
- /** Gets the next date starting from the given start date or now. */
79
- getNextDate(startDate?: Date): Date;
80
- /** Gets the specified amount of future dates starting from the given start date or now. */
81
- getNextDates(amount: number, startDate?: Date): Date[];
82
- /**
83
- * Get an ES6 compatible iterator which iterates over the next dates starting from startDate or now.
84
- * The iterator runs until the optional endDate is reached or forever.
85
- */
86
- getNextDatesIterator(startDate?: Date, endDate?: Date): Generator<Date, undefined, undefined>;
87
- /** Gets the previous date starting from the given start date or now. */
88
- getPrevDate(startDate?: Date): Date;
89
- /** Gets the specified amount of previous dates starting from the given start date or now. */
90
- getPrevDates(amount: number, startDate?: Date): Date[];
91
- /**
92
- * Get an ES6 compatible iterator which iterates over the previous dates starting from startDate or now.
93
- * The iterator runs until the optional endDate is reached or forever.
94
- */
95
- getPrevDatesIterator(startDate?: Date, endDate?: Date): Generator<Date, undefined, undefined>;
96
- /** Returns true when there is a cron date at the given date. */
97
- matchDate(date: Date): boolean;
98
- }
99
- //#endregion
100
18
  //#region ../../src/scheduler/providers/CronProvider.d.ts
101
19
  declare class CronProvider {
102
20
  protected readonly dt: DateTimeProvider;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":["ICronDefinition","Set","seconds","minutes","hours","days","months","weekdays","Cron","ReadonlyArray","Date","Generator","reversed","constructor","findAllowedHour","findAllowedMinute","findAllowedSecond","findAllowedTime","findAllowedDayInMonth","getNextDate","startDate","getNextDates","amount","getNextDatesIterator","endDate","getPrevDate","getPrevDates","getPrevDatesIterator","matchDate","date"],"sources":["../../src/scheduler/constants/CRON.ts","../../../../node_modules/cron-schedule/dist/cron.d.ts","../../src/scheduler/providers/CronProvider.ts","../../src/scheduler/primitives/$scheduler.ts","../../src/scheduler/providers/WorkerdCronProvider.ts","../../src/scheduler/index.ts"],"x_google_ignoreList":[1],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/scheduler/constants/CRON.ts","../../src/scheduler/providers/CronProvider.ts","../../src/scheduler/primitives/$scheduler.ts","../../src/scheduler/providers/WorkerdCronProvider.ts","../../src/scheduler/index.ts"],"mappings":"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"}