alepha 0.20.2 → 0.20.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) 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.js +49 -0
  7. package/dist/api/audits/index.js.map +1 -1
  8. package/dist/api/files/index.js.map +1 -1
  9. package/dist/api/jobs/index.d.ts +2 -61
  10. package/dist/api/jobs/index.d.ts.map +1 -1
  11. package/dist/api/jobs/index.js.map +1 -1
  12. package/dist/api/keys/index.d.ts +4 -4
  13. package/dist/api/keys/index.js.map +1 -1
  14. package/dist/api/notifications/index.d.ts +1 -10
  15. package/dist/api/notifications/index.d.ts.map +1 -1
  16. package/dist/api/parameters/index.browser.js +37 -0
  17. package/dist/api/parameters/index.browser.js.map +1 -1
  18. package/dist/api/parameters/index.d.ts +12 -68
  19. package/dist/api/parameters/index.d.ts.map +1 -1
  20. package/dist/api/parameters/index.js +57 -4
  21. package/dist/api/parameters/index.js.map +1 -1
  22. package/dist/api/payments/index.js.map +1 -1
  23. package/dist/api/users/index.browser.js +6 -0
  24. package/dist/api/users/index.browser.js.map +1 -1
  25. package/dist/api/users/index.d.ts +148 -227
  26. package/dist/api/users/index.d.ts.map +1 -1
  27. package/dist/api/users/index.js +60 -14
  28. package/dist/api/users/index.js.map +1 -1
  29. package/dist/api/verifications/index.d.ts.map +1 -1
  30. package/dist/api/verifications/index.js +2 -1
  31. package/dist/api/verifications/index.js.map +1 -1
  32. package/dist/bucket/index.d.ts +77 -107
  33. package/dist/bucket/index.d.ts.map +1 -1
  34. package/dist/bucket/index.js +153 -5
  35. package/dist/bucket/index.js.map +1 -1
  36. package/dist/bucket/index.workerd.js +12 -2
  37. package/dist/bucket/index.workerd.js.map +1 -1
  38. package/dist/cache/core/index.d.ts +26 -0
  39. package/dist/cache/core/index.d.ts.map +1 -1
  40. package/dist/cache/core/index.js +11 -1
  41. package/dist/cache/core/index.js.map +1 -1
  42. package/dist/cache/core/index.workerd.js +11 -1
  43. package/dist/cache/core/index.workerd.js.map +1 -1
  44. package/dist/captcha/index.js.map +1 -1
  45. package/dist/cli/config/index.d.ts +7 -5
  46. package/dist/cli/config/index.d.ts.map +1 -1
  47. package/dist/cli/config/index.js +2 -3
  48. package/dist/cli/config/index.js.map +1 -1
  49. package/dist/cli/core/index.d.ts +637 -11660
  50. package/dist/cli/core/index.d.ts.map +1 -1
  51. package/dist/cli/core/index.js +707 -532
  52. package/dist/cli/core/index.js.map +1 -1
  53. package/dist/cli/devtools/index.d.ts +4 -8
  54. package/dist/cli/devtools/index.d.ts.map +1 -1
  55. package/dist/cli/devtools/index.js +20 -16
  56. package/dist/cli/devtools/index.js.map +1 -1
  57. package/dist/cli/platform/index.d.ts +51 -77
  58. package/dist/cli/platform/index.d.ts.map +1 -1
  59. package/dist/cli/platform/index.js +65 -15
  60. package/dist/cli/platform/index.js.map +1 -1
  61. package/dist/cli/vendor/index.d.ts +10 -13
  62. package/dist/cli/vendor/index.d.ts.map +1 -1
  63. package/dist/cli/vendor/index.js +30 -12
  64. package/dist/cli/vendor/index.js.map +1 -1
  65. package/dist/command/index.js +1 -1
  66. package/dist/command/index.js.map +1 -1
  67. package/dist/core/index.browser.js +27 -3
  68. package/dist/core/index.browser.js.map +1 -1
  69. package/dist/core/index.d.ts +8 -11
  70. package/dist/core/index.d.ts.map +1 -1
  71. package/dist/core/index.js +27 -3
  72. package/dist/core/index.js.map +1 -1
  73. package/dist/core/index.native.js +27 -3
  74. package/dist/core/index.native.js.map +1 -1
  75. package/dist/core/index.workerd.js +27 -3
  76. package/dist/core/index.workerd.js.map +1 -1
  77. package/dist/crypto/index.js.map +1 -1
  78. package/dist/datetime/index.d.ts +69 -10
  79. package/dist/datetime/index.d.ts.map +1 -1
  80. package/dist/datetime/index.js +135 -13
  81. package/dist/datetime/index.js.map +1 -1
  82. package/dist/email/core/index.js.map +1 -1
  83. package/dist/email/smtp/index.js +130 -16
  84. package/dist/email/smtp/index.js.map +1 -1
  85. package/dist/fake/index.js.map +1 -1
  86. package/dist/lock/core/index.d.ts +30 -2
  87. package/dist/lock/core/index.d.ts.map +1 -1
  88. package/dist/lock/core/index.js +35 -12
  89. package/dist/lock/core/index.js.map +1 -1
  90. package/dist/lock/redis/index.js.map +1 -1
  91. package/dist/logger/index.js +32 -1
  92. package/dist/logger/index.js.map +1 -1
  93. package/dist/mcp/index.d.ts +238 -31
  94. package/dist/mcp/index.d.ts.map +1 -1
  95. package/dist/mcp/index.js +198 -67
  96. package/dist/mcp/index.js.map +1 -1
  97. package/dist/orm/core/index.browser.js +2 -362
  98. package/dist/orm/core/index.browser.js.map +1 -1
  99. package/dist/orm/core/index.bun.js +18 -409
  100. package/dist/orm/core/index.bun.js.map +1 -1
  101. package/dist/orm/core/index.d.ts +41 -194
  102. package/dist/orm/core/index.d.ts.map +1 -1
  103. package/dist/orm/core/index.js +27 -422
  104. package/dist/orm/core/index.js.map +1 -1
  105. package/dist/orm/postgres/index.bun.js +17 -20
  106. package/dist/orm/postgres/index.bun.js.map +1 -1
  107. package/dist/orm/postgres/index.d.ts +1 -5
  108. package/dist/orm/postgres/index.d.ts.map +1 -1
  109. package/dist/orm/postgres/index.js +17 -20
  110. package/dist/orm/postgres/index.js.map +1 -1
  111. package/dist/react/core/index.d.ts +102 -1
  112. package/dist/react/core/index.d.ts.map +1 -1
  113. package/dist/react/core/index.js +65 -1
  114. package/dist/react/core/index.js.map +1 -1
  115. package/dist/react/form/index.d.ts +6 -0
  116. package/dist/react/form/index.d.ts.map +1 -1
  117. package/dist/react/form/index.js +7 -7
  118. package/dist/react/form/index.js.map +1 -1
  119. package/dist/react/i18n/index.d.ts +7 -1
  120. package/dist/react/i18n/index.d.ts.map +1 -1
  121. package/dist/react/i18n/index.js +6 -0
  122. package/dist/react/i18n/index.js.map +1 -1
  123. package/dist/react/intro/index.js +22 -17
  124. package/dist/react/intro/index.js.map +1 -1
  125. package/dist/react/router/index.browser.js +98 -4
  126. package/dist/react/router/index.browser.js.map +1 -1
  127. package/dist/react/router/index.d.ts +58 -5
  128. package/dist/react/router/index.d.ts.map +1 -1
  129. package/dist/react/router/index.js +122 -6
  130. package/dist/react/router/index.js.map +1 -1
  131. package/dist/react/testing/{chunk-DBEY4PJZ.js → chunk-6Ep1yQYe.js} +1 -1
  132. package/dist/react/testing/index.js +1 -1
  133. package/dist/react/testing/index.js.map +1 -1
  134. package/dist/react/ui/index.d.ts +195 -1
  135. package/dist/react/ui/index.d.ts.map +1 -1
  136. package/dist/react/ui/index.js +64 -1
  137. package/dist/react/ui/index.js.map +1 -1
  138. package/dist/react/websocket/index.js.map +1 -1
  139. package/dist/redis/index.js.map +1 -1
  140. package/dist/scheduler/index.d.ts +1 -2
  141. package/dist/scheduler/index.d.ts.map +1 -1
  142. package/dist/scheduler/index.js +1 -1
  143. package/dist/scheduler/index.js.map +1 -1
  144. package/dist/scheduler/index.workerd.js +1 -1
  145. package/dist/scheduler/index.workerd.js.map +1 -1
  146. package/dist/security/index.browser.js.map +1 -1
  147. package/dist/security/index.d.ts.map +1 -1
  148. package/dist/security/index.js +2 -2
  149. package/dist/security/index.js.map +1 -1
  150. package/dist/server/auth/index.d.ts.map +1 -1
  151. package/dist/server/auth/index.js +24 -10
  152. package/dist/server/auth/index.js.map +1 -1
  153. package/dist/server/cookies/index.js.map +1 -1
  154. package/dist/server/core/index.browser.js +10 -3
  155. package/dist/server/core/index.browser.js.map +1 -1
  156. package/dist/server/core/index.d.ts +1 -4
  157. package/dist/server/core/index.d.ts.map +1 -1
  158. package/dist/server/core/index.js +47 -9
  159. package/dist/server/core/index.js.map +1 -1
  160. package/dist/server/links/index.browser.js.map +1 -1
  161. package/dist/server/links/index.js.map +1 -1
  162. package/dist/server/metrics/index.js +19 -1
  163. package/dist/server/metrics/index.js.map +1 -1
  164. package/dist/server/rate-limit/index.js.map +1 -1
  165. package/dist/server/static/index.js.map +1 -1
  166. package/dist/server/swagger/index.d.ts.map +1 -1
  167. package/dist/server/swagger/index.js +4 -5
  168. package/dist/server/swagger/index.js.map +1 -1
  169. package/dist/sms/index.js.map +1 -1
  170. package/dist/system/index.browser.js.map +1 -1
  171. package/dist/system/index.js.map +1 -1
  172. package/dist/system/index.workerd.js.map +1 -1
  173. package/dist/topic/core/index.js.map +1 -1
  174. package/dist/websocket/index.browser.js +32 -5
  175. package/dist/websocket/index.browser.js.map +1 -1
  176. package/dist/websocket/index.d.ts +3 -1
  177. package/dist/websocket/index.d.ts.map +1 -1
  178. package/dist/websocket/index.js +42 -6
  179. package/dist/websocket/index.js.map +1 -1
  180. package/package.json +685 -274
  181. package/src/api/files/__tests__/FileController.spec.ts +1 -1
  182. package/src/api/jobs/__tests__/$job.spec.ts +5 -1
  183. package/src/api/parameters/services/ParameterProvider.ts +21 -4
  184. package/src/api/users/__tests__/SessionService.spec.ts +99 -0
  185. package/src/api/users/__tests__/UserJobs.spec.ts +67 -0
  186. package/src/api/users/atoms/realmAuthSettingsAtom.ts +15 -0
  187. package/src/api/users/entities/sessions.ts +6 -0
  188. package/src/api/users/jobs/UserJobs.ts +44 -17
  189. package/src/api/users/providers/RealmProvider.ts +4 -0
  190. package/src/api/users/schemas/userQuerySchema.ts +0 -1
  191. package/src/api/users/services/SessionService.ts +27 -0
  192. package/src/api/users/services/UserService.ts +1 -5
  193. package/src/api/verifications/__tests__/CodeVerification.spec.ts +14 -0
  194. package/src/api/verifications/__tests__/LinkVerification.spec.ts +14 -0
  195. package/src/api/verifications/services/VerificationService.ts +1 -0
  196. package/src/bucket/__tests__/NodeS3BucketProvider.spec.ts +74 -0
  197. package/src/bucket/index.ts +19 -2
  198. package/src/bucket/primitives/$bucket.ts +9 -1
  199. package/src/bucket/providers/CloudflareR2Provider.ts +2 -137
  200. package/src/bucket/providers/NodeS3BucketProvider.ts +218 -0
  201. package/src/cache/core/index.ts +29 -0
  202. package/src/cache/core/primitives/$cache.ts +14 -1
  203. package/src/cli/config/defineConfig.ts +13 -15
  204. package/src/cli/core/__tests__/init.spec.ts +214 -7
  205. package/src/cli/core/commands/init.ts +12 -0
  206. package/src/cli/core/services/PackageManagerUtils.ts +23 -6
  207. package/src/cli/core/services/ProjectScaffolder.ts +315 -33
  208. package/src/cli/core/tasks/BuildCloudflareTask.ts +5 -0
  209. package/src/cli/core/tasks/BuildDockerTask.ts +9 -10
  210. package/src/cli/core/tasks/BuildServerTask.ts +8 -0
  211. package/src/cli/core/templates/agentMd.ts +2 -10
  212. package/src/cli/core/templates/apiIndexTs.ts +23 -1
  213. package/src/cli/core/templates/componentsJsonTs.ts +39 -0
  214. package/src/cli/core/templates/mainCss.ts +1 -0
  215. package/src/cli/core/templates/saasAdminLayoutTsx.ts +77 -0
  216. package/src/cli/core/templates/saasAdminPagesTsx.ts +26 -0
  217. package/src/cli/core/templates/saasAuthLayoutTsx.ts +20 -0
  218. package/src/cli/core/templates/saasAuthPagesTsx.ts +62 -0
  219. package/src/cli/core/templates/saasRealmProviderTs.ts +46 -0
  220. package/src/cli/core/templates/webAppRouterTs.ts +104 -1
  221. package/src/cli/core/templates/webIndexTs.ts +23 -1
  222. package/src/cli/devtools/index.ts +12 -26
  223. package/src/cli/platform/__tests__/SecretsCommand.spec.ts +2 -0
  224. package/src/cli/platform/index.ts +15 -24
  225. package/src/cli/vendor/atoms/vendorOptions.ts +1 -1
  226. package/src/cli/vendor/index.ts +14 -23
  227. package/src/command/providers/CliProvider.ts +1 -1
  228. package/src/core/Alepha.ts +11 -1
  229. package/src/core/helpers/ref.ts +18 -0
  230. package/src/core/index.shared.ts +1 -0
  231. package/src/core/interfaces/Service.ts +3 -1
  232. package/src/core/providers/SchemaValidator.ts +9 -1
  233. package/src/core/providers/TypeProvider.ts +2 -3
  234. package/src/datetime/REFACTORING.md +118 -0
  235. package/src/datetime/providers/DateTimeProvider.ts +203 -24
  236. package/src/lock/core/index.ts +31 -0
  237. package/src/lock/core/primitives/$lock.ts +14 -1
  238. package/src/logger/services/Logger.ts +1 -1
  239. package/src/mcp/__tests__/$resource.spec.ts +1 -1
  240. package/src/mcp/__tests__/$tool.spec.ts +1 -1
  241. package/src/mcp/__tests__/McpServerProvider.spec.ts +1 -1
  242. package/src/mcp/__tests__/jsonrpc.spec.ts +1 -1
  243. package/src/mcp/helpers/jsonrpc.ts +26 -1
  244. package/src/mcp/index.ts +10 -5
  245. package/src/mcp/interfaces/McpTypes.ts +83 -6
  246. package/src/mcp/primitives/$prompt.ts +18 -1
  247. package/src/mcp/primitives/$resource.ts +18 -1
  248. package/src/mcp/primitives/$tool.ts +83 -7
  249. package/src/mcp/providers/McpServerProvider.ts +74 -16
  250. package/src/mcp/transports/StreamableHttpMcpTransport.ts +226 -0
  251. package/src/orm/REFACTORING.md +330 -0
  252. package/src/orm/__tests__/$repository-tests.ts +1 -0
  253. package/src/orm/__tests__/orm-next-tests.ts +2 -67
  254. package/src/orm/__tests__/orm-next.spec.ts +0 -21
  255. package/src/orm/core/index.shared.ts +0 -2
  256. package/src/orm/core/index.ts +1 -2
  257. package/src/orm/core/primitives/$repository.ts +3 -6
  258. package/src/orm/core/primitives/$transactional.ts +11 -0
  259. package/src/orm/core/providers/drivers/DatabaseProvider.ts +0 -5
  260. package/src/orm/core/providers/drivers/NodeSqliteProvider.ts +11 -13
  261. package/src/orm/core/schemas/updateSchema.ts +1 -1
  262. package/src/orm/core/services/ModelBuilder.ts +1 -13
  263. package/src/orm/core/services/PgRelationManager.ts +4 -2
  264. package/src/orm/core/services/Repository.ts +1 -42
  265. package/src/orm/core/services/SqliteModelBuilder.ts +2 -33
  266. package/src/orm/postgres/services/PostgresModelBuilder.ts +10 -45
  267. package/src/react/core/__tests__/useQuery.browser.spec.tsx +86 -0
  268. package/src/react/core/hooks/useQuery.ts +153 -0
  269. package/src/react/core/index.ts +1 -0
  270. package/src/react/form/services/FormModel.ts +15 -6
  271. package/src/react/form/services/parseField.ts +8 -0
  272. package/src/react/i18n/providers/I18nProvider.ts +8 -2
  273. package/src/react/intro/components/GettingStartedAuthSlide.tsx +11 -4
  274. package/src/react/router/__tests__/$page.spec.tsx +0 -16
  275. package/src/react/router/__tests__/ReactBrowserProvider.browser.spec.ts +213 -2
  276. package/src/react/router/__tests__/ssr.spec.tsx +339 -0
  277. package/src/react/router/primitives/$page.ts +28 -4
  278. package/src/react/router/providers/ReactBrowserProvider.ts +73 -0
  279. package/src/react/router/providers/ReactBrowserRouterProvider.ts +1 -1
  280. package/src/react/router/providers/ReactPageProvider.ts +27 -9
  281. package/src/react/router/providers/ReactPreloadProvider.ts +1 -1
  282. package/src/react/router/providers/ReactServerProvider.ts +1 -0
  283. package/src/react/ui/atoms/uiThemeListAtom.ts +36 -0
  284. package/src/react/ui/index.ts +6 -0
  285. package/src/react/ui/services/SchemaControl.ts +209 -0
  286. package/src/scheduler/providers/CronProvider.ts +1 -1
  287. package/src/security/primitives/$basicAuth.ts +1 -1
  288. package/src/security/primitives/$issuer.ts +6 -3
  289. package/src/server/auth/providers/ServerAuthProvider.ts +5 -1
  290. package/src/server/core/__tests__/ServerRouterProvider-serializationError.spec.ts +75 -0
  291. package/src/server/core/__tests__/ServerRouterProvider-validationError.spec.ts +306 -0
  292. package/src/server/core/errors/ValidationError.ts +13 -1
  293. package/src/server/core/interfaces/ServerRequest.ts +1 -0
  294. package/src/server/core/primitives/$action.ts +16 -5
  295. package/src/server/core/providers/ServerProvider.ts +1 -1
  296. package/src/server/core/providers/ServerRouterProvider.ts +28 -6
  297. package/src/server/core/services/HttpClient.ts +1 -1
  298. package/src/server/swagger/providers/ServerSwaggerProvider.ts +6 -8
  299. package/src/websocket/providers/NodeWebSocketServerProvider.ts +10 -4
  300. package/src/websocket/services/WebSocketClient.ts +11 -5
  301. package/src/mcp/transports/SseMcpTransport.ts +0 -182
  302. package/src/orm/core/__tests__/parseQueryString.spec.ts +0 -196
  303. package/src/orm/core/helpers/parseQueryString.ts +0 -502
  304. package/src/orm/core/primitives/$view.ts +0 -88
@@ -827,7 +827,7 @@ export class Alepha {
827
827
  }
828
828
 
829
829
  /**
830
- * Alias for {@link Alepha#with}.
830
+ * @alias {@link Alepha#with}.
831
831
  */
832
832
  public register<T extends object>(
833
833
  serviceEntry: ServiceEntry<T> | { default: ServiceEntry<T> },
@@ -849,6 +849,16 @@ export class Alepha {
849
849
  opts.parent !== undefined ? opts.parent : (__alephaRef?.parent ?? Alepha);
850
850
 
851
851
  const transient = lifetime === "transient";
852
+ // TODO: warn-once when scoped lifetime silently falls back to the global
853
+ // singleton registry. This happens when AsyncLocalStorage is not available
854
+ // (typically in the browser, where AlsProvider is a no-op) — the user
855
+ // asked for per-request isolation and got a cross-request singleton.
856
+ // Today this is silent. Plan: detect (this.context.get("registry") === undefined)
857
+ // on a "scoped" inject, log a one-shot warning via the Logger module
858
+ // ("[alepha] scoped DI requested for <Service> but no AsyncLocalStorage
859
+ // context — falling back to singleton. This is expected in browser
860
+ // builds; in server runtimes ensure the request is wrapped in
861
+ // alepha.context.run()."). Gate behind a Set<Service> so we don't spam.
852
862
  const registry =
853
863
  lifetime === "scoped"
854
864
  ? (this.context.get<Map<Service, ServiceDefinition>>("registry") ??
@@ -54,4 +54,22 @@ export const __alephaRef: {
54
54
  *
55
55
  * One main goal of Alepha is working with classes but without the class verbosity.
56
56
  * Forcing to pass Alepha instance in constructors would be a step back in that direction!
57
+ *
58
+ * ---------------------------------------------------------------------------------------------------------------------
59
+ *
60
+ * TODO: harden the cursor against mid-instantiation throws.
61
+ *
62
+ * Today, the cleanup of `__alephaRef.alepha`, `__alephaRef.service`, `__alephaRef.parent`
63
+ * is performed by the caller after the synchronous instantiation returns. If the
64
+ * instantiation throws (e.g., a primitive factory blows up, or a user constructor
65
+ * raises), the cursor can be left holding stale references until the next
66
+ * instantiation overwrites them. In practice this is harmless because Alepha is
67
+ * single-threaded synchronous during boot and the next inject() rewrites the
68
+ * cursor — but it is a sharp edge for debuggers (frame inspection shows ghost
69
+ * state) and any future tooling that introspects __alephaRef out-of-band.
70
+ *
71
+ * Plan: wrap each cursor mutation in Alepha.ts in a try/finally that snapshots
72
+ * the previous value and restores it on exit (even on throw). Equivalent to a
73
+ * lexically-scoped "with" — pushes/pops cleanly. Should be a 5-line refactor in
74
+ * Alepha.ts:1131-1132 once we touch it.
57
75
  */
@@ -36,6 +36,7 @@ export * from "./providers/Json.ts";
36
36
  export * from "./providers/JsonSchemaCodec.ts";
37
37
  export * from "./providers/KeylessJsonSchemaCodec.ts";
38
38
  export * from "./providers/SchemaCodec.ts";
39
+ export * from "./providers/SchemaValidator.ts";
39
40
  export * from "./providers/StateManager.ts";
40
41
  export * from "./providers/TypeProvider.ts";
41
42
  export * from "./schemas/pageQuerySchema.ts";
@@ -8,7 +8,9 @@ export type Service<T extends object = any> =
8
8
  | AbstractClass<T>
9
9
  | RunFunction<T>;
10
10
 
11
- export type RunFunction<T extends object = any> = (...args: any[]) => T | void;
11
+ export type RunFunction<T extends object = any> = (
12
+ ...args: any[]
13
+ ) => T | undefined;
12
14
 
13
15
  export type InstantiableClass<T extends object = any> = new (
14
16
  ...args: any[]
@@ -1,8 +1,9 @@
1
1
  import type { TSchema } from "typebox";
2
2
  import { Compile, type Validator } from "typebox/compile";
3
+ import * as Value from "typebox/value";
3
4
  import { TypeBoxError } from "../errors/TypeBoxError.ts";
4
5
  import { $hook } from "../primitives/$hook.ts";
5
- import { type Static, t, Value } from "./TypeProvider.ts";
6
+ import { type Static, t } from "./TypeProvider.ts";
6
7
 
7
8
  export class SchemaValidator {
8
9
  protected cache = new Map<TSchema, Validator>();
@@ -38,6 +39,13 @@ export class SchemaValidator {
38
39
  }
39
40
  }
40
41
 
42
+ /**
43
+ * Deep clone a schema. Useful when a schema is going to be mutated.
44
+ */
45
+ public clone<T extends TSchema>(schema: T): T {
46
+ return Value.Clone(schema);
47
+ }
48
+
41
49
  protected getValidator<T extends TSchema>(schema: T): Validator<{}, T> {
42
50
  if (this.cache.has(schema)) {
43
51
  return this.cache.get(schema) as Validator<{}, T>;
@@ -27,12 +27,11 @@ import type {
27
27
  import { Type } from "typebox";
28
28
  import Format from "typebox/format";
29
29
  import { Settings } from "typebox/system";
30
- import * as Value from "typebox/value";
31
30
  import { OPTIONS } from "../constants/OPTIONS.ts";
32
31
  import type { TypeBoxError } from "../errors/TypeBoxError.ts";
33
32
  import { isTypeFile, type TFile, type TStream } from "../helpers/FileLike.ts";
34
33
 
35
- export { Format, Type, Value };
34
+ export { Format, Type };
36
35
 
37
36
  // https://github.com/sinclairzx81/typebox/blob/main/changelog/1.1.0.md
38
37
  Settings.Set({ correctiveParse: true });
@@ -447,7 +446,7 @@ export class TypeProvider {
447
446
  /**
448
447
  * Create a schema that maps all properties of an object schema to nullable.
449
448
  */
450
- public nullify = <T extends TSchema>(schema: T, options?: TObjectOptions) =>
449
+ public nullify = (schema: TSchema, options?: TObjectOptions): TSchema =>
451
450
  Type.Mapped(
452
451
  Type.Identifier("K"),
453
452
  Type.KeyOf(schema),
@@ -0,0 +1,118 @@
1
+ # DateTime — Refactoring Roadmap
2
+
3
+ ## Context
4
+
5
+ Step 1 (done) isolated dayjs behind two wrapper classes — `DateTime` and `Duration` — exposed by `DateTimeProvider`. Nothing in the codebase imports dayjs directly anymore; only this module does.
6
+
7
+ Step 2 (this document) is the engine swap: replace the dayjs internals of those wrappers with `Temporal` + `Intl`, then remove dayjs entirely. **Public API stays identical**, so call-sites across the framework do not change.
8
+
9
+ This refactoring is **deferred** until `Temporal` is natively available across our supported runtimes (Node, Bun, modern browsers). Today (2026-05) only Firefox ships it stably; V8/JSC are still flagged. Pulling the trigger before then would force every consumer to ship a ~28 KB polyfill — a regression vs the current dayjs footprint.
10
+
11
+ When the time comes, the migration is contained to **two files** (`DateTimeProvider.ts` and `react/i18n/providers/I18nProvider.ts`) plus `package.json`.
12
+
13
+ ## Polyfill policy
14
+
15
+ `alepha` will **not** bundle or depend on a Temporal polyfill. Apps that need to support runtimes without native Temporal install one themselves and load it once at startup:
16
+
17
+ ```ts
18
+ // app entry point
19
+ import "temporal-polyfill/global";
20
+ ```
21
+
22
+ Rationale:
23
+ - Polyfill choice is an app concern (bundle size, runtime targets).
24
+ - The polyfill is global-only by nature; importing it from a library would force it on every consumer regardless of need.
25
+ - Once native support is universal, the polyfill becomes a no-op and apps drop the import. `alepha` itself never had to change.
26
+
27
+ The framework's documentation should mention the polyfill recommendation in the upgrade notes for the release that performs the swap.
28
+
29
+ ## Wrappers — what changes inside
30
+
31
+ ### `DateTime`
32
+
33
+ Currently wraps `Dayjs`. After the swap it wraps a `Temporal.Instant` (and an optional timezone string for the `tz()` chain). Per-method translation:
34
+
35
+ | Method | Temporal mapping |
36
+ |---|---|
37
+ | `add(n, unit)` / `add(Duration)` | `instant.add(Temporal.Duration.from({ [unit]: n }))` |
38
+ | `subtract(n, unit)` / `subtract(Duration)` | `instant.subtract(...)` |
39
+ | `startOf(unit)` / `endOf(unit)` | Convert to `ZonedDateTime`, zero out lower fields, convert back. ~10 lines per unit. No native equivalent. |
40
+ | `isAfter` / `isBefore` / `isSame` | `Temporal.Instant.compare(a, b)` |
41
+ | `diff(other, unit?)` | `instant.since(other).total({ unit })` (default unit: `"milliseconds"`) |
42
+ | `tz(zone)` | Store `zone` on the wrapper; convert via `instant.toZonedDateTimeISO(zone)` when needed for formatting/`startOf`. |
43
+ | `locale(lang)` | Store `lang` on the wrapper (consumed only by `format`/`fromNow`). Or drop the chained form and pass `lang` directly. |
44
+ | `format(template?)` | See **Format tokens** below. |
45
+ | `fromNow(withoutSuffix?)` | `Intl.RelativeTimeFormat` — pick the largest non-zero unit from `since(now)`. ~25 lines. Needs `lang` (currently inherited from `.locale()` chain). |
46
+ | `toISOString()` | `instant.toString()` |
47
+ | `toDate()` | `new Date(instant.epochMilliseconds)` |
48
+ | `valueOf()` | `instant.epochMilliseconds` |
49
+ | `unix()` | `Math.floor(instant.epochMilliseconds / 1000)` |
50
+ | `toJSON` / `toString` | same as `toISOString()` |
51
+ | `toDayjs()` | **Removed.** Verify with grep first — currently used only inside `DateTimeProvider`. |
52
+
53
+ ### `Duration`
54
+
55
+ Currently wraps `dayjsDuration.Duration`. After the swap it wraps `Temporal.Duration`. Per-method translation:
56
+
57
+ | Method | Temporal mapping |
58
+ |---|---|
59
+ | `asMilliseconds()` etc. | `duration.total({ unit: "milliseconds" })` etc. |
60
+ | `as(unit)` | `duration.total({ unit })` |
61
+ | `toISOString()` | `duration.toString()` (Temporal already emits ISO 8601) |
62
+ | `toDayjs()` | **Removed.** |
63
+
64
+ Constructor input normalization (number, `[n, unit]` tuple, ISO string `"PT5M"`, existing `Duration`) stays in `DateTimeProvider.duration()`. Temporal parses ISO strings natively via `Temporal.Duration.from()`; the tuple form needs a 3-line shim.
65
+
66
+ ## Format tokens — the one open decision
67
+
68
+ `DateTime.format(template)` currently delegates to dayjs and supports two flavours:
69
+
70
+ 1. **Localized presets** — `"L"`, `"LL"`, `"LLL"`, `"lll"`, etc. (from `dayjs/plugin/localizedFormat`).
71
+ 2. **Token strings** — `"YYYY-MM-DD HH:mm:ss"`.
72
+
73
+ `Intl.DateTimeFormat` uses option objects, not tokens. Two paths:
74
+
75
+ **(a) Drop tokens — preferred.** Force callers to use `Intl.DateTimeFormat` options. `I18nProvider.l` already supports this path (`options.date` accepts an `Intl.DateTimeFormatOptions` object). The token-string path becomes the fallback to remove. Audit before removal — current callers of `format(token)`:
76
+
77
+ - `react/i18n/providers/I18nProvider.ts` — `dt.locale(lang).format(options.date)` when `options.date` is a string
78
+ - `scheduler/providers/WorkerdCronProvider.ts` — `now.format()` (no template — equivalent to ISO string)
79
+
80
+ If those are the only callers, deletion is a 5-line change.
81
+
82
+ **(b) Token mapper.** Keep tokens, write a small dayjs-token → `Intl.DateTimeFormat` translator (~80 lines for `YYYY/MM/DD/HH/mm/ss/L/LL/LLL/lll`). Higher maintenance cost, no real ergonomic win. Recommend against unless external consumers depend on the token form.
83
+
84
+ ## I18n provider changes
85
+
86
+ `react/i18n/providers/I18nProvider.ts` is the only file outside `datetime/` that exercises the soon-to-change parts of the API:
87
+
88
+ - `dt.tz(timezone)` — unchanged signature, unchanged caller code.
89
+ - `dt.locale(lang).fromNow()` — becomes `dt.fromNow(lang)` (or `relativeFormat(dt, lang)` helper). Caller updated.
90
+ - `dt.locale(lang).format(token)` — covered by the format-token decision above.
91
+ - The `Intl.DateTimeFormat`/`Intl.NumberFormat` paths in this file already work and are unaffected.
92
+
93
+ ## Test surface
94
+
95
+ - `datetime/__tests__/DateTimeProvider.spec.ts` — exercises `pause`/`travel`/`createTimeout`/`wait`/`deadline`/`duration`/`isDurationLike`. All operate on epoch-ms internals; should pass unchanged.
96
+ - All ~3400 other tests across the framework hit the wrappers via the public API. They are the safety net.
97
+
98
+ ## Removal checklist
99
+
100
+ When Step 2 ships:
101
+
102
+ 1. `package.json` — remove `dayjs`. No new dep added (polyfill is app-side).
103
+ 2. `DateTimeProvider.ts` — remove all `dayjs/*` imports, the `PLUGINS` static, the `for (plugin of PLUGINS) DayjsApi.extend(plugin)` constructor block, and the `unwrap`/`toDayjs` helpers.
104
+ 3. `DateTime.toDayjs()` and `Duration.toDayjs()` — delete.
105
+ 4. Re-exported dayjs unit types (`ManipulateType`, `OpUnitType`, `QUnitType`, `DurationUnitType`) — replace with locally-defined union types covering the units we actually use. Keep the names if they're referenced externally; just stop sourcing them from dayjs.
106
+ 5. `react/i18n/providers/I18nProvider.ts` — adjust per the I18n section above.
107
+ 6. Verify: `grep -r dayjs packages apps` returns nothing.
108
+ 7. Run `yarn lint && yarn typecheck && yarn test`.
109
+
110
+ ## Why not now
111
+
112
+ - Polyfill weight: temporal-polyfill ~28 KB gz vs current dayjs+plugins+locales ~15–20 KB gz. Net regression until native lands.
113
+ - No functional gain for users today — same behaviour, same wire format (ISO 8601 strings via `t.datetime()`), same testing affordances (`pause`/`travel`).
114
+ - The cost of waiting is zero: Step 1 already gave us the abstraction. The engine swap is mechanical when the time is right.
115
+
116
+ ## Estimated effort when triggered
117
+
118
+ ~1 focused day. The wrapper rewrite is mechanical, the I18n adjustment is small, and the test suite catches regressions immediately.
@@ -9,24 +9,189 @@ import { $hook, $inject, Alepha } from "alepha";
9
9
  import DayjsApi, {
10
10
  type Dayjs,
11
11
  type ManipulateType,
12
+ type OpUnitType,
12
13
  type PluginFunc,
14
+ type QUnitType,
13
15
  } from "dayjs";
14
- import dayjsDuration from "dayjs/plugin/duration.js";
16
+ import dayjsDuration, { type DurationUnitType } from "dayjs/plugin/duration.js";
15
17
  import dayjsLocalizedFormat from "dayjs/plugin/localizedFormat.js";
16
18
  import dayjsRelativeTime from "dayjs/plugin/relativeTime.js";
17
19
  import dayjsTimezone from "dayjs/plugin/timezone.js";
18
20
  import dayjsUtc from "dayjs/plugin/utc.js";
19
21
 
20
- export type DateTime = DayjsApi.Dayjs;
21
- export type Duration = dayjsDuration.Duration;
22
- export type DurationLike =
23
- | number
24
- | dayjsDuration.Duration
25
- | [number, ManipulateType];
22
+ export type { DurationUnitType, ManipulateType, OpUnitType, QUnitType };
23
+
24
+ export type DateTimeInput = string | number | Date | DateTime | Dayjs;
25
+
26
+ export type DurationLike = number | Duration | [number, ManipulateType];
27
+
28
+ /**
29
+ * Immutable wrapper around the underlying date-time engine.
30
+ *
31
+ * Designed to isolate consumers from the engine in use (currently dayjs).
32
+ * Methods that produce a new value return a new `DateTime` instance.
33
+ */
34
+ export class DateTime {
35
+ protected readonly inner: Dayjs;
36
+
37
+ constructor(inner: Dayjs) {
38
+ this.inner = inner;
39
+ }
40
+
41
+ /**
42
+ * Add a duration to this date-time.
43
+ */
44
+ add(amount: number, unit?: ManipulateType): DateTime;
45
+ add(duration: Duration): DateTime;
46
+ add(amount: number | Duration, unit?: ManipulateType): DateTime {
47
+ if (amount instanceof Duration) {
48
+ return new DateTime(this.inner.add(amount.toDayjs()));
49
+ }
50
+ return new DateTime(this.inner.add(amount, unit));
51
+ }
52
+
53
+ /**
54
+ * Subtract a duration from this date-time.
55
+ */
56
+ subtract(amount: number, unit?: ManipulateType): DateTime;
57
+ subtract(duration: Duration): DateTime;
58
+ subtract(amount: number | Duration, unit?: ManipulateType): DateTime {
59
+ if (amount instanceof Duration) {
60
+ return new DateTime(this.inner.subtract(amount.toDayjs()));
61
+ }
62
+ return new DateTime(this.inner.subtract(amount, unit));
63
+ }
64
+
65
+ startOf(unit: OpUnitType): DateTime {
66
+ return new DateTime(this.inner.startOf(unit));
67
+ }
68
+
69
+ endOf(unit: OpUnitType): DateTime {
70
+ return new DateTime(this.inner.endOf(unit));
71
+ }
72
+
73
+ isAfter(other: DateTimeInput): boolean {
74
+ return this.inner.isAfter(toDayjs(other));
75
+ }
76
+
77
+ isBefore(other: DateTimeInput): boolean {
78
+ return this.inner.isBefore(toDayjs(other));
79
+ }
80
+
81
+ isSame(other: DateTimeInput, unit?: OpUnitType): boolean {
82
+ return this.inner.isSame(toDayjs(other), unit);
83
+ }
84
+
85
+ diff(other: DateTimeInput, unit?: QUnitType | OpUnitType): number {
86
+ return this.inner.diff(toDayjs(other), unit);
87
+ }
88
+
89
+ tz(timezone: string): DateTime {
90
+ return new DateTime(this.inner.tz(timezone));
91
+ }
92
+
93
+ locale(lang: string): DateTime {
94
+ return new DateTime(this.inner.locale(lang));
95
+ }
96
+
97
+ format(template?: string): string {
98
+ return this.inner.format(template);
99
+ }
100
+
101
+ fromNow(withoutSuffix?: boolean): string {
102
+ return this.inner.fromNow(withoutSuffix);
103
+ }
104
+
105
+ toISOString(): string {
106
+ return this.inner.toISOString();
107
+ }
108
+
109
+ toDate(): Date {
110
+ return this.inner.toDate();
111
+ }
112
+
113
+ valueOf(): number {
114
+ return this.inner.valueOf();
115
+ }
116
+
117
+ unix(): number {
118
+ return this.inner.unix();
119
+ }
120
+
121
+ toJSON(): string {
122
+ return this.inner.toISOString();
123
+ }
124
+
125
+ toString(): string {
126
+ return this.inner.toISOString();
127
+ }
128
+
129
+ /**
130
+ * Escape hatch for the underlying dayjs instance.
131
+ *
132
+ * Use sparingly — anything calling this becomes coupled to dayjs and
133
+ * will need to migrate when the engine is replaced.
134
+ */
135
+ toDayjs(): Dayjs {
136
+ return this.inner;
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Immutable wrapper around the underlying duration engine.
142
+ */
143
+ export class Duration {
144
+ protected readonly inner: dayjsDuration.Duration;
145
+
146
+ constructor(inner: dayjsDuration.Duration) {
147
+ this.inner = inner;
148
+ }
149
+
150
+ asMilliseconds(): number {
151
+ return this.inner.asMilliseconds();
152
+ }
153
+
154
+ asSeconds(): number {
155
+ return this.inner.asSeconds();
156
+ }
157
+
158
+ asMinutes(): number {
159
+ return this.inner.asMinutes();
160
+ }
161
+
162
+ asHours(): number {
163
+ return this.inner.asHours();
164
+ }
165
+
166
+ asDays(): number {
167
+ return this.inner.asDays();
168
+ }
169
+
170
+ as(unit: DurationUnitType): number {
171
+ return this.inner.as(unit);
172
+ }
173
+
174
+ toISOString(): string {
175
+ return this.inner.toISOString();
176
+ }
177
+
178
+ /**
179
+ * Escape hatch for the underlying dayjs duration.
180
+ */
181
+ toDayjs(): dayjsDuration.Duration {
182
+ return this.inner;
183
+ }
184
+ }
26
185
 
27
- export const dayjs = DayjsApi;
28
186
  export const isDateTime = (value: unknown): value is DateTime => {
29
- return dayjs.isDayjs(value);
187
+ return value instanceof DateTime;
188
+ };
189
+
190
+ const toDayjs = (value: DateTimeInput): Dayjs => {
191
+ if (value instanceof DateTime) {
192
+ return value.toDayjs();
193
+ }
194
+ return DayjsApi(value as any);
30
195
  };
31
196
 
32
197
  export class DateTimeProvider {
@@ -45,7 +210,7 @@ export class DateTimeProvider {
45
210
 
46
211
  constructor() {
47
212
  for (const plugin of DateTimeProvider.PLUGINS) {
48
- dayjs.extend(plugin);
213
+ DayjsApi.extend(plugin);
49
214
  }
50
215
  }
51
216
 
@@ -81,33 +246,34 @@ export class DateTimeProvider {
81
246
  });
82
247
 
83
248
  public setLocale(locale: string): void {
84
- dayjs.locale(locale);
249
+ DayjsApi.locale(locale);
85
250
  }
86
251
 
87
252
  public isDateTime(value: unknown): value is DateTime {
88
- return dayjs.isDayjs(value);
253
+ return value instanceof DateTime;
89
254
  }
90
255
 
91
256
  /**
92
257
  * Create a new UTC DateTime instance.
93
258
  */
94
- public utc(
95
- date: string | number | Date | Dayjs | null | undefined,
96
- ): DateTime {
97
- return dayjs.utc(date);
259
+ public utc(date: DateTimeInput | null | undefined): DateTime {
260
+ return new DateTime(DayjsApi.utc(unwrap(date)));
98
261
  }
99
262
 
100
263
  /**
101
264
  * Create a new DateTime instance.
102
265
  */
103
- public of(date: string | number | Date | Dayjs | null | undefined): DateTime {
104
- return dayjs(date);
266
+ public of(date: DateTimeInput | null | undefined): DateTime {
267
+ if (date instanceof DateTime) {
268
+ return date;
269
+ }
270
+ return new DateTime(DayjsApi(date as any));
105
271
  }
106
272
 
107
273
  /**
108
274
  * Get the current date as a string.
109
275
  */
110
- public toISOString(date: Date | string | DateTime = this.now()): string {
276
+ public toISOString(date: DateTimeInput = this.now()): string {
111
277
  return this.of(date).toISOString();
112
278
  }
113
279
 
@@ -152,7 +318,7 @@ export class DateTimeProvider {
152
318
  return this.ref;
153
319
  }
154
320
 
155
- return dayjs();
321
+ return new DateTime(DayjsApi());
156
322
  }
157
323
 
158
324
  /**
@@ -162,12 +328,16 @@ export class DateTimeProvider {
162
328
  duration: DurationLike,
163
329
  unit?: ManipulateType,
164
330
  ): Duration => {
331
+ if (duration instanceof Duration) {
332
+ return duration;
333
+ }
334
+
165
335
  if (Array.isArray(duration)) {
166
- return dayjs.duration(duration[0], duration[1]);
336
+ return new Duration(DayjsApi.duration(duration[0], duration[1]));
167
337
  }
168
338
 
169
339
  if (typeof duration === "number") {
170
- return dayjs.duration(duration, unit || "milliseconds");
340
+ return new Duration(DayjsApi.duration(duration, unit || "milliseconds"));
171
341
  }
172
342
 
173
343
  return duration;
@@ -175,7 +345,9 @@ export class DateTimeProvider {
175
345
 
176
346
  public isDurationLike(value: unknown): value is DurationLike {
177
347
  try {
178
- return dayjs.isDuration(this.duration(value as DurationLike));
348
+ return DayjsApi.isDuration(
349
+ this.duration(value as DurationLike).toDayjs(),
350
+ );
179
351
  } catch {
180
352
  return false;
181
353
  }
@@ -261,7 +433,7 @@ export class DateTimeProvider {
261
433
  ): Timeout {
262
434
  if (this.ref && now) {
263
435
  const next = this.of(now).add(this.duration(duration));
264
- if (next < this.now()) {
436
+ if (next.valueOf() < this.now().valueOf()) {
265
437
  callback();
266
438
  }
267
439
  return {
@@ -403,6 +575,13 @@ export class DateTimeProvider {
403
575
  }
404
576
  }
405
577
 
578
+ const unwrap = (value: DateTimeInput | null | undefined): any => {
579
+ if (value instanceof DateTime) {
580
+ return value.toDayjs();
581
+ }
582
+ return value;
583
+ };
584
+
406
585
  export interface Interval {
407
586
  timer?: any;
408
587
  duration: number;
@@ -13,6 +13,37 @@ export * from "./providers/MemoryLockProvider.ts";
13
13
 
14
14
  // ---------------------------------------------------------------------------------------------------------------------
15
15
 
16
+ declare module "alepha" {
17
+ interface Hooks {
18
+ /**
19
+ * Fires when a lock is successfully acquired.
20
+ */
21
+ "lock:acquired": {
22
+ name: string;
23
+ id: string;
24
+ maxDurationMs: number;
25
+ };
26
+ /**
27
+ * Fires when a lock is released (handler completed or threw).
28
+ */
29
+ "lock:released": {
30
+ name: string;
31
+ id: string;
32
+ heldMs: number;
33
+ };
34
+ /**
35
+ * Fires when a lock acquisition contends with another holder.
36
+ * Emitted whether the caller eventually acquires (`wait: true`) or fails.
37
+ */
38
+ "lock:contended": {
39
+ name: string;
40
+ id: string;
41
+ };
42
+ }
43
+ }
44
+
45
+ // ---------------------------------------------------------------------------------------------------------------------
46
+
16
47
  /**
17
48
  * Resource locking for distributed systems.
18
49
  *
@@ -43,7 +43,7 @@ export const $lock = (options: LockMiddlewareOptions): Middleware => {
43
43
  return createMiddleware({
44
44
  name: "$lock",
45
45
  options: options as unknown as Record<string, unknown>,
46
- handler: ({ next }) => {
46
+ handler: ({ alepha, next }) => {
47
47
  const id = crypto.randomUUID();
48
48
  const maxDurationMs = dateTimeProvider
49
49
  .duration(options.maxDuration ?? [5, "minutes"])
@@ -71,11 +71,13 @@ export const $lock = (options: LockMiddlewareOptions): Middleware => {
71
71
 
72
72
  // Lock already ended (grace period active)
73
73
  if (endedAtStr) {
74
+ await alepha.events.emit("lock:contended", { name, id });
74
75
  throw new LockAcquireError(name);
75
76
  }
76
77
 
77
78
  // Lock held by someone else
78
79
  if (lockId !== id) {
80
+ await alepha.events.emit("lock:contended", { name, id });
79
81
  if (options.wait) {
80
82
  // Poll until lock is released
81
83
  const start = dateTimeProvider.nowMillis();
@@ -109,10 +111,21 @@ export const $lock = (options: LockMiddlewareOptions): Middleware => {
109
111
  }
110
112
 
111
113
  // We hold the lock — execute handler
114
+ const acquiredAt = dateTimeProvider.nowMillis();
115
+ await alepha.events.emit("lock:acquired", {
116
+ name,
117
+ id,
118
+ maxDurationMs,
119
+ });
112
120
  try {
113
121
  return await next(...args);
114
122
  } finally {
115
123
  await lockProvider.del(name);
124
+ await alepha.events.emit("lock:released", {
125
+ name,
126
+ id,
127
+ heldMs: dateTimeProvider.nowMillis() - acquiredAt,
128
+ });
116
129
  }
117
130
  };
118
131
  },
@@ -146,7 +146,7 @@ export class Logger implements LoggerInterface {
146
146
  }
147
147
 
148
148
  let _data: object | Error | undefined;
149
- if (typeof data === "object" && !!data) {
149
+ if (typeof data === "object" && data) {
150
150
  _data = data;
151
151
  } else if (typeof message === "object" && message) {
152
152
  _data = message;
@@ -364,7 +364,7 @@ describe("$resource primitive", () => {
364
364
  uri: "protected://resource",
365
365
  handler: async ({ context }) => {
366
366
  const authHeader = context?.headers?.authorization;
367
- if (!authHeader || !authHeader.toString().startsWith("Bearer ")) {
367
+ if (!authHeader?.toString().startsWith("Bearer ")) {
368
368
  throw new Error("Unauthorized");
369
369
  }
370
370
  return { text: "Secret content" };
@@ -379,7 +379,7 @@ describe("$tool primitive", () => {
379
379
  },
380
380
  handler: async ({ context }) => {
381
381
  const authHeader = context?.headers?.authorization;
382
- if (!authHeader || !authHeader.toString().startsWith("Bearer ")) {
382
+ if (!authHeader?.toString().startsWith("Bearer ")) {
383
383
  throw new Error("Unauthorized");
384
384
  }
385
385
  return "Access granted";
@@ -751,7 +751,7 @@ describe("McpServerProvider", () => {
751
751
  schema: { result: t.text() },
752
752
  handler: async ({ context }) => {
753
753
  const auth = context?.headers?.authorization;
754
- if (!auth || !auth.toString().startsWith("Bearer ")) {
754
+ if (!auth?.toString().startsWith("Bearer ")) {
755
755
  throw new Error("Unauthorized");
756
756
  }
757
757
  return "Access granted";