alepha 0.14.4 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/README.md +1 -4
  2. package/dist/api/audits/index.d.ts +619 -731
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/files/index.d.ts +185 -298
  5. package/dist/api/files/index.d.ts.map +1 -1
  6. package/dist/api/files/index.js +0 -1
  7. package/dist/api/files/index.js.map +1 -1
  8. package/dist/api/jobs/index.d.ts +245 -356
  9. package/dist/api/jobs/index.d.ts.map +1 -1
  10. package/dist/api/notifications/index.d.ts +238 -350
  11. package/dist/api/notifications/index.d.ts.map +1 -1
  12. package/dist/api/parameters/index.d.ts +499 -611
  13. package/dist/api/parameters/index.d.ts.map +1 -1
  14. package/dist/api/users/index.browser.js +1 -2
  15. package/dist/api/users/index.browser.js.map +1 -1
  16. package/dist/api/users/index.d.ts +1697 -1804
  17. package/dist/api/users/index.d.ts.map +1 -1
  18. package/dist/api/users/index.js +178 -151
  19. package/dist/api/users/index.js.map +1 -1
  20. package/dist/api/verifications/index.d.ts +132 -132
  21. package/dist/api/verifications/index.d.ts.map +1 -1
  22. package/dist/batch/index.d.ts +122 -122
  23. package/dist/batch/index.d.ts.map +1 -1
  24. package/dist/batch/index.js +1 -2
  25. package/dist/batch/index.js.map +1 -1
  26. package/dist/bucket/index.d.ts +163 -163
  27. package/dist/bucket/index.d.ts.map +1 -1
  28. package/dist/cache/core/index.d.ts +46 -46
  29. package/dist/cache/core/index.d.ts.map +1 -1
  30. package/dist/cache/redis/index.d.ts.map +1 -1
  31. package/dist/cli/index.d.ts +302 -299
  32. package/dist/cli/index.d.ts.map +1 -1
  33. package/dist/cli/index.js +966 -564
  34. package/dist/cli/index.js.map +1 -1
  35. package/dist/command/index.d.ts +303 -299
  36. package/dist/command/index.d.ts.map +1 -1
  37. package/dist/command/index.js +11 -7
  38. package/dist/command/index.js.map +1 -1
  39. package/dist/core/index.browser.js +419 -99
  40. package/dist/core/index.browser.js.map +1 -1
  41. package/dist/core/index.d.ts +718 -625
  42. package/dist/core/index.d.ts.map +1 -1
  43. package/dist/core/index.js +420 -99
  44. package/dist/core/index.js.map +1 -1
  45. package/dist/core/index.native.js +419 -99
  46. package/dist/core/index.native.js.map +1 -1
  47. package/dist/datetime/index.d.ts +44 -44
  48. package/dist/datetime/index.d.ts.map +1 -1
  49. package/dist/datetime/index.js +4 -4
  50. package/dist/datetime/index.js.map +1 -1
  51. package/dist/email/index.d.ts +97 -50
  52. package/dist/email/index.d.ts.map +1 -1
  53. package/dist/email/index.js +129 -33
  54. package/dist/email/index.js.map +1 -1
  55. package/dist/fake/index.d.ts +7981 -14
  56. package/dist/fake/index.d.ts.map +1 -1
  57. package/dist/file/index.d.ts +523 -390
  58. package/dist/file/index.d.ts.map +1 -1
  59. package/dist/file/index.js +253 -1
  60. package/dist/file/index.js.map +1 -1
  61. package/dist/lock/core/index.d.ts +208 -208
  62. package/dist/lock/core/index.d.ts.map +1 -1
  63. package/dist/lock/redis/index.d.ts.map +1 -1
  64. package/dist/logger/index.d.ts +25 -26
  65. package/dist/logger/index.d.ts.map +1 -1
  66. package/dist/mcp/index.d.ts +197 -197
  67. package/dist/mcp/index.d.ts.map +1 -1
  68. package/dist/orm/chunk-DtkW-qnP.js +38 -0
  69. package/dist/orm/index.browser.js.map +1 -1
  70. package/dist/orm/index.bun.js +2814 -0
  71. package/dist/orm/index.bun.js.map +1 -0
  72. package/dist/orm/index.d.ts +1205 -1057
  73. package/dist/orm/index.d.ts.map +1 -1
  74. package/dist/orm/index.js +2056 -1753
  75. package/dist/orm/index.js.map +1 -1
  76. package/dist/queue/core/index.d.ts +248 -248
  77. package/dist/queue/core/index.d.ts.map +1 -1
  78. package/dist/queue/redis/index.d.ts.map +1 -1
  79. package/dist/redis/index.bun.js +285 -0
  80. package/dist/redis/index.bun.js.map +1 -0
  81. package/dist/redis/index.d.ts +118 -136
  82. package/dist/redis/index.d.ts.map +1 -1
  83. package/dist/redis/index.js +18 -38
  84. package/dist/redis/index.js.map +1 -1
  85. package/dist/retry/index.d.ts +69 -69
  86. package/dist/retry/index.d.ts.map +1 -1
  87. package/dist/router/index.d.ts +6 -6
  88. package/dist/router/index.d.ts.map +1 -1
  89. package/dist/scheduler/index.d.ts +25 -25
  90. package/dist/scheduler/index.d.ts.map +1 -1
  91. package/dist/security/index.browser.js +5 -1
  92. package/dist/security/index.browser.js.map +1 -1
  93. package/dist/security/index.d.ts +417 -254
  94. package/dist/security/index.d.ts.map +1 -1
  95. package/dist/security/index.js +386 -86
  96. package/dist/security/index.js.map +1 -1
  97. package/dist/server/auth/index.d.ts +277 -277
  98. package/dist/server/auth/index.d.ts.map +1 -1
  99. package/dist/server/auth/index.js +20 -20
  100. package/dist/server/auth/index.js.map +1 -1
  101. package/dist/server/cache/index.d.ts +60 -57
  102. package/dist/server/cache/index.d.ts.map +1 -1
  103. package/dist/server/cache/index.js +1 -1
  104. package/dist/server/cache/index.js.map +1 -1
  105. package/dist/server/compress/index.d.ts +3 -3
  106. package/dist/server/compress/index.d.ts.map +1 -1
  107. package/dist/server/cookies/index.d.ts +6 -6
  108. package/dist/server/cookies/index.d.ts.map +1 -1
  109. package/dist/server/cookies/index.js +3 -3
  110. package/dist/server/cookies/index.js.map +1 -1
  111. package/dist/server/core/index.d.ts +242 -150
  112. package/dist/server/core/index.d.ts.map +1 -1
  113. package/dist/server/core/index.js +288 -122
  114. package/dist/server/core/index.js.map +1 -1
  115. package/dist/server/cors/index.d.ts +11 -12
  116. package/dist/server/cors/index.d.ts.map +1 -1
  117. package/dist/server/health/index.d.ts +0 -1
  118. package/dist/server/health/index.d.ts.map +1 -1
  119. package/dist/server/helmet/index.d.ts +2 -2
  120. package/dist/server/helmet/index.d.ts.map +1 -1
  121. package/dist/server/links/index.browser.js.map +1 -1
  122. package/dist/server/links/index.d.ts +84 -85
  123. package/dist/server/links/index.d.ts.map +1 -1
  124. package/dist/server/links/index.js +1 -2
  125. package/dist/server/links/index.js.map +1 -1
  126. package/dist/server/metrics/index.d.ts.map +1 -1
  127. package/dist/server/multipart/index.d.ts +6 -6
  128. package/dist/server/multipart/index.d.ts.map +1 -1
  129. package/dist/server/proxy/index.d.ts +102 -103
  130. package/dist/server/proxy/index.d.ts.map +1 -1
  131. package/dist/server/rate-limit/index.d.ts +16 -16
  132. package/dist/server/rate-limit/index.d.ts.map +1 -1
  133. package/dist/server/static/index.d.ts +44 -44
  134. package/dist/server/static/index.d.ts.map +1 -1
  135. package/dist/server/swagger/index.d.ts +48 -49
  136. package/dist/server/swagger/index.d.ts.map +1 -1
  137. package/dist/server/swagger/index.js +1 -2
  138. package/dist/server/swagger/index.js.map +1 -1
  139. package/dist/sms/index.d.ts +13 -11
  140. package/dist/sms/index.d.ts.map +1 -1
  141. package/dist/sms/index.js +7 -7
  142. package/dist/sms/index.js.map +1 -1
  143. package/dist/thread/index.d.ts +71 -72
  144. package/dist/thread/index.d.ts.map +1 -1
  145. package/dist/topic/core/index.d.ts +318 -318
  146. package/dist/topic/core/index.d.ts.map +1 -1
  147. package/dist/topic/redis/index.d.ts +6 -6
  148. package/dist/topic/redis/index.d.ts.map +1 -1
  149. package/dist/vite/index.d.ts +5720 -159
  150. package/dist/vite/index.d.ts.map +1 -1
  151. package/dist/vite/index.js +41 -18
  152. package/dist/vite/index.js.map +1 -1
  153. package/dist/websocket/index.browser.js +6 -6
  154. package/dist/websocket/index.browser.js.map +1 -1
  155. package/dist/websocket/index.d.ts +247 -247
  156. package/dist/websocket/index.d.ts.map +1 -1
  157. package/dist/websocket/index.js +6 -6
  158. package/dist/websocket/index.js.map +1 -1
  159. package/package.json +9 -14
  160. package/src/api/files/controllers/AdminFileStatsController.ts +0 -1
  161. package/src/api/users/atoms/realmAuthSettingsAtom.ts +5 -0
  162. package/src/api/users/controllers/{UserRealmController.ts → RealmController.ts} +11 -11
  163. package/src/api/users/entities/users.ts +1 -1
  164. package/src/api/users/index.ts +8 -8
  165. package/src/api/users/primitives/{$userRealm.ts → $realm.ts} +17 -19
  166. package/src/api/users/providers/{UserRealmProvider.ts → RealmProvider.ts} +26 -30
  167. package/src/api/users/schemas/{userRealmConfigSchema.ts → realmConfigSchema.ts} +2 -2
  168. package/src/api/users/services/CredentialService.ts +7 -7
  169. package/src/api/users/services/IdentityService.ts +4 -4
  170. package/src/api/users/services/RegistrationService.spec.ts +25 -27
  171. package/src/api/users/services/RegistrationService.ts +38 -27
  172. package/src/api/users/services/SessionCrudService.ts +3 -3
  173. package/src/api/users/services/SessionService.spec.ts +3 -3
  174. package/src/api/users/services/SessionService.ts +28 -9
  175. package/src/api/users/services/UserService.ts +7 -7
  176. package/src/batch/providers/BatchProvider.ts +1 -2
  177. package/src/cli/apps/AlephaPackageBuilderCli.ts +38 -19
  178. package/src/cli/assets/apiHelloControllerTs.ts +18 -0
  179. package/src/cli/assets/apiIndexTs.ts +16 -0
  180. package/src/cli/assets/claudeMd.ts +303 -0
  181. package/src/cli/assets/mainBrowserTs.ts +2 -2
  182. package/src/cli/assets/mainServerTs.ts +24 -0
  183. package/src/cli/assets/webAppRouterTs.ts +15 -0
  184. package/src/cli/assets/webHelloComponentTsx.ts +16 -0
  185. package/src/cli/assets/webIndexTs.ts +16 -0
  186. package/src/cli/commands/build.ts +41 -21
  187. package/src/cli/commands/db.ts +21 -18
  188. package/src/cli/commands/deploy.ts +17 -5
  189. package/src/cli/commands/dev.ts +13 -17
  190. package/src/cli/commands/format.ts +8 -2
  191. package/src/cli/commands/init.ts +74 -29
  192. package/src/cli/commands/lint.ts +8 -2
  193. package/src/cli/commands/test.ts +8 -2
  194. package/src/cli/commands/typecheck.ts +5 -1
  195. package/src/cli/commands/verify.ts +4 -2
  196. package/src/cli/services/AlephaCliUtils.ts +39 -600
  197. package/src/cli/services/PackageManagerUtils.ts +301 -0
  198. package/src/cli/services/ProjectScaffolder.ts +306 -0
  199. package/src/command/helpers/Runner.ts +15 -3
  200. package/src/core/__tests__/Alepha-graph.spec.ts +4 -0
  201. package/src/core/index.shared.ts +1 -0
  202. package/src/core/index.ts +2 -0
  203. package/src/core/primitives/$hook.ts +6 -2
  204. package/src/core/primitives/$module.spec.ts +4 -0
  205. package/src/core/providers/AlsProvider.ts +1 -1
  206. package/src/core/providers/CodecManager.spec.ts +12 -6
  207. package/src/core/providers/CodecManager.ts +26 -6
  208. package/src/core/providers/EventManager.ts +169 -13
  209. package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +621 -0
  210. package/src/core/providers/KeylessJsonSchemaCodec.ts +407 -0
  211. package/src/core/providers/StateManager.spec.ts +27 -16
  212. package/src/email/providers/LocalEmailProvider.spec.ts +111 -87
  213. package/src/email/providers/LocalEmailProvider.ts +52 -15
  214. package/src/email/providers/NodemailerEmailProvider.ts +167 -56
  215. package/src/file/errors/FileError.ts +7 -0
  216. package/src/file/index.ts +9 -1
  217. package/src/file/providers/MemoryFileSystemProvider.ts +393 -0
  218. package/src/orm/index.browser.ts +1 -19
  219. package/src/orm/index.bun.ts +77 -0
  220. package/src/orm/index.shared-server.ts +22 -0
  221. package/src/orm/index.shared.ts +15 -0
  222. package/src/orm/index.ts +19 -39
  223. package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -5
  224. package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
  225. package/src/orm/providers/drivers/CloudflareD1Provider.ts +4 -0
  226. package/src/orm/providers/drivers/DatabaseProvider.ts +4 -0
  227. package/src/orm/providers/drivers/PglitePostgresProvider.ts +4 -0
  228. package/src/orm/services/Repository.ts +8 -0
  229. package/src/redis/index.bun.ts +35 -0
  230. package/src/redis/providers/BunRedisProvider.ts +12 -43
  231. package/src/redis/providers/BunRedisSubscriberProvider.ts +2 -3
  232. package/src/redis/providers/NodeRedisProvider.ts +16 -34
  233. package/src/{server/security → security}/__tests__/BasicAuth.spec.ts +11 -11
  234. package/src/{server/security → security}/__tests__/ServerSecurityProvider-realm.spec.ts +21 -16
  235. package/src/{server/security/providers → security/__tests__}/ServerSecurityProvider.spec.ts +5 -5
  236. package/src/security/index.browser.ts +5 -0
  237. package/src/security/index.ts +90 -7
  238. package/src/security/primitives/{$realm.spec.ts → $issuer.spec.ts} +11 -11
  239. package/src/security/primitives/{$realm.ts → $issuer.ts} +20 -17
  240. package/src/security/primitives/$role.ts +5 -5
  241. package/src/security/primitives/$serviceAccount.spec.ts +5 -5
  242. package/src/security/primitives/$serviceAccount.ts +3 -3
  243. package/src/{server/security → security}/providers/ServerSecurityProvider.ts +5 -7
  244. package/src/server/auth/primitives/$auth.ts +10 -10
  245. package/src/server/auth/primitives/$authCredentials.ts +3 -3
  246. package/src/server/auth/primitives/$authGithub.ts +3 -3
  247. package/src/server/auth/primitives/$authGoogle.ts +3 -3
  248. package/src/server/auth/providers/ServerAuthProvider.ts +13 -13
  249. package/src/server/cache/providers/ServerCacheProvider.ts +1 -1
  250. package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -3
  251. package/src/server/core/providers/NodeHttpServerProvider.ts +25 -6
  252. package/src/server/core/providers/ServerBodyParserProvider.ts +19 -23
  253. package/src/server/core/providers/ServerLoggerProvider.ts +23 -19
  254. package/src/server/core/providers/ServerProvider.ts +144 -21
  255. package/src/server/core/providers/ServerRouterProvider.ts +259 -115
  256. package/src/server/core/providers/ServerTimingProvider.ts +2 -2
  257. package/src/server/links/index.ts +1 -1
  258. package/src/server/links/providers/LinkProvider.ts +1 -1
  259. package/src/server/swagger/index.ts +1 -1
  260. package/src/sms/providers/LocalSmsProvider.spec.ts +153 -111
  261. package/src/sms/providers/LocalSmsProvider.ts +8 -7
  262. package/src/vite/helpers/boot.ts +28 -17
  263. package/src/vite/tasks/buildServer.ts +12 -1
  264. package/src/vite/tasks/devServer.ts +3 -1
  265. package/src/vite/tasks/generateCloudflare.ts +7 -0
  266. package/dist/server/security/index.browser.js +0 -13
  267. package/dist/server/security/index.browser.js.map +0 -1
  268. package/dist/server/security/index.d.ts +0 -173
  269. package/dist/server/security/index.d.ts.map +0 -1
  270. package/dist/server/security/index.js +0 -311
  271. package/dist/server/security/index.js.map +0 -1
  272. package/src/cli/assets/appRouterTs.ts +0 -9
  273. package/src/cli/assets/mainTs.ts +0 -13
  274. package/src/server/security/index.browser.ts +0 -10
  275. package/src/server/security/index.ts +0 -94
  276. /package/src/{server/security → security}/primitives/$basicAuth.ts +0 -0
  277. /package/src/{server/security → security}/providers/ServerBasicAuthProvider.ts +0 -0
@@ -82,12 +82,16 @@ export class HookPrimitive<T extends keyof Hooks> extends Primitive<
82
82
  public called = 0;
83
83
 
84
84
  protected onInit() {
85
+ // Store reference to handler to avoid property access in hot path
86
+ const handler = this.options.handler;
87
+
85
88
  this.alepha.events.on(this.options.on, {
86
89
  caller: this.config.service,
87
90
  priority: this.options.priority,
88
- callback: async (args: any) => {
91
+ // Return handler result directly - EventManager checks if it's a promise
92
+ callback: (args: any) => {
89
93
  this.called += 1;
90
- await this.options.handler(args);
94
+ return handler(args);
91
95
  },
92
96
  });
93
97
  }
@@ -51,6 +51,10 @@ describe("$module", () => {
51
51
  from: ["StateManager", "CodecManager"],
52
52
  module: "alepha.core",
53
53
  },
54
+ KeylessJsonSchemaCodec: {
55
+ from: ["CodecManager"],
56
+ module: "alepha.core",
57
+ },
54
58
  CodecManager: {
55
59
  from: ["Alepha"],
56
60
  module: "alepha.core",
@@ -25,7 +25,7 @@ export class AlsProvider {
25
25
  data.registry ??= new Map();
26
26
  data.context ??= this.createContextId();
27
27
 
28
- return this.als.run(data, callback);
28
+ return this.als.run({ ...data }, callback);
29
29
  }
30
30
 
31
31
  public exists(): boolean {
@@ -44,7 +44,10 @@ describe("CodecManager", () => {
44
44
  const codecManager = alepha.codec;
45
45
  const customCodec = new CustomCodec();
46
46
 
47
- codecManager.register("custom", customCodec);
47
+ codecManager.register({
48
+ name: "custom",
49
+ codec: customCodec,
50
+ });
48
51
 
49
52
  const retrieved = codecManager.getCodec("custom");
50
53
  expect(retrieved).toBe(customCodec);
@@ -74,10 +77,13 @@ describe("CodecManager", () => {
74
77
 
75
78
  const alepha = Alepha.create();
76
79
  const codecManager = alepha.codec;
77
- codecManager.register("mock", new MockCodec());
80
+ codecManager.register({
81
+ name: "mock",
82
+ codec: new MockCodec(),
83
+ });
78
84
 
79
85
  expect(() => codecManager.getCodec("missing")).toThrow(
80
- 'Codec "missing" not found. Available codecs: json, mock',
86
+ 'Codec "missing" not found. Available codecs: json, keyless, mock',
81
87
  );
82
88
  });
83
89
  });
@@ -298,7 +304,7 @@ describe("CodecManager", () => {
298
304
 
299
305
  const alepha = Alepha.create();
300
306
  const codecManager = alepha.codec;
301
- codecManager.register("custom", new CustomCodec());
307
+ codecManager.register({ name: "custom", codec: new CustomCodec() });
302
308
 
303
309
  const schema = t.object({ value: t.text() });
304
310
  const result = codecManager.encode(
@@ -513,7 +519,7 @@ describe("CodecManager", () => {
513
519
 
514
520
  const alepha = Alepha.create();
515
521
  const codecManager = alepha.codec;
516
- codecManager.register("custom", new CustomCodec());
522
+ codecManager.register({ name: "custom", codec: new CustomCodec() });
517
523
 
518
524
  const schema = t.object({ value: t.text() });
519
525
  const result = codecManager.decode(
@@ -631,7 +637,7 @@ describe("CodecManager", () => {
631
637
  codec = $inject(CodecManager);
632
638
 
633
639
  constructor() {
634
- this.codec.register("custom", new CustomCodec());
640
+ this.codec.register({ name: "custom", codec: new CustomCodec() });
635
641
  }
636
642
  }
637
643
 
@@ -2,6 +2,7 @@ import type { StaticEncode, TSchema } from "typebox";
2
2
  import { AlephaError } from "../errors/AlephaError.ts";
3
3
  import { $inject } from "../primitives/$inject.ts";
4
4
  import { JsonSchemaCodec } from "./JsonSchemaCodec.ts";
5
+ import { KeylessJsonSchemaCodec } from "./KeylessJsonSchemaCodec.ts";
5
6
  import type { SchemaCodec } from "./SchemaCodec.ts";
6
7
  import { SchemaValidator, type ValidateOptions } from "./SchemaValidator.ts";
7
8
  import type { Static } from "./TypeProvider.ts";
@@ -61,23 +62,34 @@ export interface DecodeOptions {
61
62
  export class CodecManager {
62
63
  protected readonly codecs: Map<string, SchemaCodec> = new Map();
63
64
  protected readonly jsonCodec = $inject(JsonSchemaCodec);
65
+ protected readonly keylessCodec = $inject(KeylessJsonSchemaCodec);
64
66
  protected readonly schemaValidator = $inject(SchemaValidator);
65
67
 
66
68
  public default = "json";
67
69
 
68
70
  constructor() {
69
71
  // Register default JSON codec
70
- this.register(this.default, this.jsonCodec);
72
+ this.register({
73
+ name: "json",
74
+ codec: this.jsonCodec,
75
+ default: true,
76
+ });
77
+
78
+ // Register keyless JSON codec (smaller, faster decoding)
79
+ this.register({
80
+ name: "keyless",
81
+ codec: this.keylessCodec,
82
+ });
71
83
  }
72
84
 
73
85
  /**
74
86
  * Register a new codec format.
75
- *
76
- * @param name - The name of the codec (e.g., 'json', 'protobuf')
77
- * @param codec - The codec implementation
78
87
  */
79
- public register(name: string, codec: SchemaCodec): void {
80
- this.codecs.set(name, codec);
88
+ public register(opts: CodecRegisterOptions): void {
89
+ this.codecs.set(opts.name, opts.codec);
90
+ if (opts.default) {
91
+ this.default = opts.name;
92
+ }
81
93
  }
82
94
 
83
95
  /**
@@ -164,3 +176,11 @@ export class CodecManager {
164
176
  return this.schemaValidator.validate(schema, value, options);
165
177
  }
166
178
  }
179
+
180
+ // ---------------------------------------------------------------------------------------------------------------------
181
+
182
+ export interface CodecRegisterOptions {
183
+ name: string;
184
+ codec: SchemaCodec;
185
+ default?: boolean;
186
+ }
@@ -3,6 +3,23 @@ import { AlephaError } from "../errors/AlephaError.ts";
3
3
  import type { Async } from "../interfaces/Async.ts";
4
4
  import type { LoggerInterface } from "../interfaces/LoggerInterface.ts";
5
5
 
6
+ /**
7
+ * Compiled event executor - optimized for hot paths.
8
+ * Returns void for sync-only chains, Promise<void> for chains with async hooks.
9
+ */
10
+ export type CompiledEventExecutor<T> = (payload: T) => void | Promise<void>;
11
+
12
+ /**
13
+ * Options for compiled event executors.
14
+ */
15
+ export interface CompileOptions {
16
+ /**
17
+ * If true, errors will be caught and logged instead of throwing.
18
+ * @default false
19
+ */
20
+ catch?: boolean;
21
+ }
22
+
6
23
  export class EventManager {
7
24
  public logFn?: () => LoggerInterface | undefined;
8
25
 
@@ -59,8 +76,114 @@ export class EventManager {
59
76
  };
60
77
  }
61
78
 
79
+ /**
80
+ * Compiles an event into an optimized executor function.
81
+ *
82
+ * Call this after all hooks are registered (e.g., after Alepha.start()).
83
+ * The returned function checks each hook's return value and awaits promises.
84
+ * Returns undefined if all hooks are sync, or a Promise if any hook returns one.
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * // At startup (after hooks are registered)
89
+ * const onRequest = alepha.events.compile("server:onRequest", { catch: true });
90
+ *
91
+ * // In hot path - only await if promise returned
92
+ * const result = onRequest({ request, route });
93
+ * if (result) await result;
94
+ * ```
95
+ */
96
+ public compile<T extends keyof Hooks>(
97
+ event: T,
98
+ options: CompileOptions = {},
99
+ ): CompiledEventExecutor<Hooks[T]> {
100
+ const hooks = this.events[event];
101
+
102
+ // No hooks - return no-op
103
+ if (!hooks || hooks.length === 0) {
104
+ return () => {};
105
+ }
106
+
107
+ const catchErrors = options.catch ?? false;
108
+ const log = this.log;
109
+
110
+ // Helper to run remaining hooks sequentially after first async hook
111
+ const runRemainingAsync = async (
112
+ startIndex: number,
113
+ payload: Hooks[T],
114
+ ): Promise<void> => {
115
+ for (let i = startIndex; i < hooks.length; i++) {
116
+ const hook = hooks[i];
117
+ try {
118
+ const result = hook.callback(payload);
119
+ if (result && typeof result === "object" && "then" in result) {
120
+ if (catchErrors) {
121
+ await (result as Promise<void>).catch((error) => {
122
+ log?.error(
123
+ `${String(event)}(${hook.caller?.name ?? "unknown"}) ERROR`,
124
+ error,
125
+ );
126
+ });
127
+ } else {
128
+ await result;
129
+ }
130
+ }
131
+ } catch (error) {
132
+ if (catchErrors) {
133
+ log?.error(
134
+ `${String(event)}(${hook.caller?.name ?? "unknown"}) ERROR`,
135
+ error,
136
+ );
137
+ } else {
138
+ throw error;
139
+ }
140
+ }
141
+ }
142
+ };
143
+
144
+ // Return executor that runs sync hooks synchronously, then switches to async
145
+ // when encountering the first async hook. Returns void if all sync.
146
+ return (payload: Hooks[T]): void | Promise<void> => {
147
+ for (let i = 0; i < hooks.length; i++) {
148
+ const hook = hooks[i];
149
+ try {
150
+ const result = hook.callback(payload);
151
+ if (result && typeof result === "object" && "then" in result) {
152
+ // Hit an async hook - await it and continue remaining hooks async
153
+ if (catchErrors) {
154
+ return (result as Promise<void>)
155
+ .catch((error) => {
156
+ log?.error(
157
+ `${String(event)}(${hook.caller?.name ?? "unknown"}) ERROR`,
158
+ error,
159
+ );
160
+ })
161
+ .then(() => runRemainingAsync(i + 1, payload));
162
+ }
163
+ return (result as Promise<void>).then(() =>
164
+ runRemainingAsync(i + 1, payload),
165
+ );
166
+ }
167
+ } catch (error) {
168
+ if (catchErrors) {
169
+ log?.error(
170
+ `${String(event)}(${hook.caller?.name ?? "unknown"}) ERROR`,
171
+ error,
172
+ );
173
+ } else {
174
+ throw error;
175
+ }
176
+ }
177
+ }
178
+ // All hooks were sync - return void
179
+ };
180
+ }
181
+
62
182
  /**
63
183
  * Emits the specified event with the given payload.
184
+ *
185
+ * For hot paths (like HTTP request handling), use compile() instead
186
+ * to get an optimized executor.
64
187
  */
65
188
  public async emit<T extends keyof Hooks>(
66
189
  func: T,
@@ -87,36 +210,65 @@ export class EventManager {
87
210
  catch?: boolean;
88
211
  } = {},
89
212
  ): Promise<void> {
213
+ // Fast path: no listeners for this event
214
+ const events = this.events[func];
215
+ if (!events || events.length === 0) {
216
+ return;
217
+ }
218
+
219
+ // Fast path: single listener, no logging, no reverse
220
+ if (events.length === 1 && !options.log && !options.reverse) {
221
+ const hook = events[0];
222
+ try {
223
+ const result = hook.callback(payload);
224
+ if (result && typeof result === "object" && "then" in result) {
225
+ await result;
226
+ }
227
+ } catch (error) {
228
+ if (options.catch) {
229
+ this.log?.error(
230
+ `${String(func)}(${hook.caller?.name ?? "unknown"}) ERROR`,
231
+ error,
232
+ );
233
+ return;
234
+ }
235
+ throw error;
236
+ }
237
+ return;
238
+ }
239
+
90
240
  const ctx: any = {};
91
241
 
92
242
  if (options.log) {
93
- ctx.now = Date.now();
94
- this.log?.trace(`${func} ...`);
243
+ ctx.now = performance.now();
244
+ this.log?.trace(`${String(func)} ...`);
95
245
  }
96
246
 
97
- let events = this.events[func] ?? [];
98
-
247
+ let eventList = events;
99
248
  if (options.reverse) {
100
- events = events.toReversed();
249
+ eventList = events.toReversed();
101
250
  }
102
251
 
103
- for (const hook of events) {
252
+ for (const hook of eventList) {
104
253
  const name = hook.caller?.name ?? "unknown";
105
254
  if (options.log) {
106
- ctx.now2 = Date.now();
107
- this.log?.trace(`${func}(${name}) ...`);
255
+ ctx.now2 = performance.now();
256
+ this.log?.trace(`${String(func)}(${name}) ...`);
108
257
  }
109
258
 
110
259
  try {
111
- await hook.callback(payload);
260
+ const result = hook.callback(payload);
261
+ if (result && typeof result === "object" && "then" in result) {
262
+ await result;
263
+ }
112
264
  } catch (error) {
113
265
  if (options.catch) {
114
- this.log?.error(`${func}(${name}) ERROR`, error);
266
+ this.log?.error(`${String(func)}(${name}) ERROR`, error);
115
267
  continue;
116
268
  }
117
269
  if (options.log) {
118
270
  throw new AlephaError(
119
- `Failed during '${func}()' hook for service: ${name}`,
271
+ `Failed during '${String(func)}()' hook for service: ${name}`,
120
272
  { cause: error },
121
273
  );
122
274
  }
@@ -124,12 +276,16 @@ export class EventManager {
124
276
  }
125
277
 
126
278
  if (options.log) {
127
- this.log?.debug(`${func}(${name}) OK [${Date.now() - ctx.now2}ms]`);
279
+ this.log?.debug(
280
+ `${String(func)}(${name}) OK [${(performance.now() - ctx.now2).toFixed(1)}ms]`,
281
+ );
128
282
  }
129
283
  }
130
284
 
131
285
  if (options.log) {
132
- this.log?.debug(`${func} OK [${Date.now() - ctx.now}ms]`);
286
+ this.log?.debug(
287
+ `${String(func)} OK [${(performance.now() - ctx.now).toFixed(1)}ms]`,
288
+ );
133
289
  }
134
290
  }
135
291
  }