alepha 0.13.2 → 0.13.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 (76) hide show
  1. package/dist/api-files/index.browser.js +80 -0
  2. package/dist/api-files/index.browser.js.map +1 -0
  3. package/dist/api-jobs/index.browser.js +56 -0
  4. package/dist/api-jobs/index.browser.js.map +1 -0
  5. package/dist/api-notifications/index.browser.js +382 -0
  6. package/dist/api-notifications/index.browser.js.map +1 -0
  7. package/dist/api-notifications/index.d.ts +124 -69
  8. package/dist/api-notifications/index.js +107 -55
  9. package/dist/api-notifications/index.js.map +1 -1
  10. package/dist/api-parameters/index.browser.js +29 -0
  11. package/dist/api-parameters/index.browser.js.map +1 -0
  12. package/dist/api-users/index.d.ts +16 -3
  13. package/dist/api-users/index.js +75 -28
  14. package/dist/api-users/index.js.map +1 -1
  15. package/dist/api-verifications/index.browser.js +52 -0
  16. package/dist/api-verifications/index.browser.js.map +1 -0
  17. package/dist/api-verifications/index.d.ts +117 -95
  18. package/dist/api-verifications/index.js +1 -1
  19. package/dist/api-verifications/index.js.map +1 -1
  20. package/dist/batch/index.js +0 -5
  21. package/dist/batch/index.js.map +1 -1
  22. package/dist/bucket/index.js +7 -5
  23. package/dist/bucket/index.js.map +1 -1
  24. package/dist/cli/{dist-Dl9Vl7Ur.js → dist-lGnqsKpu.js} +11 -15
  25. package/dist/cli/dist-lGnqsKpu.js.map +1 -0
  26. package/dist/cli/index.d.ts +26 -45
  27. package/dist/cli/index.js +40 -58
  28. package/dist/cli/index.js.map +1 -1
  29. package/dist/command/index.d.ts +1 -0
  30. package/dist/command/index.js +9 -0
  31. package/dist/command/index.js.map +1 -1
  32. package/dist/email/index.js +5 -0
  33. package/dist/email/index.js.map +1 -1
  34. package/dist/orm/index.js +3 -3
  35. package/dist/orm/index.js.map +1 -1
  36. package/dist/redis/index.d.ts +10 -10
  37. package/dist/security/index.d.ts +28 -28
  38. package/dist/security/index.js +3 -3
  39. package/dist/security/index.js.map +1 -1
  40. package/dist/server/index.d.ts +9 -9
  41. package/dist/server-auth/index.d.ts +152 -152
  42. package/dist/server-cookies/index.js +2 -2
  43. package/dist/server-cookies/index.js.map +1 -1
  44. package/dist/server-links/index.d.ts +33 -33
  45. package/dist/server-static/index.js +18 -2
  46. package/dist/server-static/index.js.map +1 -1
  47. package/package.json +16 -6
  48. package/src/api-files/index.browser.ts +17 -0
  49. package/src/api-jobs/index.browser.ts +15 -0
  50. package/src/api-notifications/controllers/NotificationController.ts +26 -1
  51. package/src/api-notifications/index.browser.ts +17 -0
  52. package/src/api-notifications/index.ts +1 -0
  53. package/src/api-notifications/schemas/notificationQuerySchema.ts +13 -0
  54. package/src/api-notifications/services/NotificationService.ts +45 -2
  55. package/src/api-parameters/index.browser.ts +12 -0
  56. package/src/api-users/atoms/realmAuthSettingsAtom.ts +3 -1
  57. package/src/api-users/controllers/UserController.ts +21 -1
  58. package/src/api-users/primitives/$userRealm.ts +33 -10
  59. package/src/api-users/providers/UserRealmProvider.ts +1 -0
  60. package/src/api-users/services/SessionService.ts +2 -0
  61. package/src/api-users/services/UserService.ts +56 -16
  62. package/src/api-verifications/index.browser.ts +15 -0
  63. package/src/api-verifications/index.ts +1 -0
  64. package/src/batch/providers/BatchProvider.ts +0 -7
  65. package/src/bucket/index.ts +7 -5
  66. package/src/cli/apps/AlephaCli.ts +27 -1
  67. package/src/cli/apps/AlephaPackageBuilderCli.ts +3 -0
  68. package/src/cli/commands/CoreCommands.ts +6 -2
  69. package/src/cli/commands/ViteCommands.ts +2 -1
  70. package/src/cli/services/ProjectUtils.ts +40 -75
  71. package/src/command/helpers/Asker.ts +10 -0
  72. package/src/email/index.ts +13 -5
  73. package/src/orm/providers/drivers/NodeSqliteProvider.ts +3 -3
  74. package/src/server-cookies/providers/ServerCookiesProvider.ts +2 -1
  75. package/src/server-static/providers/ServerStaticProvider.ts +18 -3
  76. package/dist/cli/dist-Dl9Vl7Ur.js.map +0 -1
@@ -23,12 +23,22 @@ export class UserService {
23
23
 
24
24
  /**
25
25
  * Request email verification for a user.
26
+ * @param email - The email address to verify.
27
+ * @param userRealmName - Optional realm name.
28
+ * @param method - The verification method: "code" (default) or "link".
29
+ * @param verifyUrl - Base URL for verification link (required when method is "link").
26
30
  */
27
31
  public async requestEmailVerification(
28
32
  email: string,
29
33
  userRealmName?: string,
34
+ method: "code" | "link" = "code",
35
+ verifyUrl?: string,
30
36
  ): Promise<boolean> {
31
- this.log.trace("Requesting email verification", { email, userRealmName });
37
+ this.log.trace("Requesting email verification", {
38
+ email,
39
+ userRealmName,
40
+ method,
41
+ });
32
42
 
33
43
  const user = await this.users(userRealmName)
34
44
  .findOne({
@@ -54,23 +64,47 @@ export class UserService {
54
64
  try {
55
65
  const verification =
56
66
  await this.verificationController.requestVerificationCode({
57
- params: { type: "code" },
67
+ params: { type: method },
58
68
  body: { target: email },
59
69
  });
60
70
 
61
- await this.userNotifications.emailVerification.push({
62
- contact: email,
63
- variables: {
71
+ if (method === "link") {
72
+ // Build verification URL with token
73
+ const url = new URL(verifyUrl || "/verify-email", "http://localhost");
74
+ url.searchParams.set("email", email);
75
+ url.searchParams.set("token", verification.token);
76
+ const fullVerifyUrl = verifyUrl
77
+ ? `${verifyUrl}${url.search}`
78
+ : url.pathname + url.search;
79
+
80
+ await this.userNotifications.emailVerificationLink.push({
81
+ contact: email,
82
+ variables: {
83
+ email,
84
+ verifyUrl: fullVerifyUrl,
85
+ expiresInMinutes: Math.floor(verification.codeExpiration / 60),
86
+ },
87
+ });
88
+
89
+ this.log.debug("Email verification link sent", {
64
90
  email,
65
- code: verification.token,
66
- expiresInMinutes: Math.floor(verification.codeExpiration / 60),
67
- },
68
- });
91
+ userId: user.id,
92
+ });
93
+ } else {
94
+ await this.userNotifications.emailVerification.push({
95
+ contact: email,
96
+ variables: {
97
+ email,
98
+ code: verification.token,
99
+ expiresInMinutes: Math.floor(verification.codeExpiration / 60),
100
+ },
101
+ });
69
102
 
70
- this.log.debug("Email verification code sent", {
71
- email,
72
- userId: user.id,
73
- });
103
+ this.log.debug("Email verification code sent", {
104
+ email,
105
+ userId: user.id,
106
+ });
107
+ }
74
108
  } catch (error) {
75
109
  // Silent fail for security
76
110
  this.log.warn("Failed to send email verification", { email, error });
@@ -81,6 +115,7 @@ export class UserService {
81
115
 
82
116
  /**
83
117
  * Verify a user's email using a valid verification token.
118
+ * Supports both code (6-digit) and link (UUID) verification tokens.
84
119
  */
85
120
  public async verifyEmail(
86
121
  email: string,
@@ -89,13 +124,18 @@ export class UserService {
89
124
  ): Promise<void> {
90
125
  this.log.trace("Verifying email", { email, userRealmName });
91
126
 
127
+ // Detect verification type based on token format
128
+ // Codes are 6-digit numbers, links are UUIDs
129
+ const isCode = /^\d{6}$/.test(token);
130
+ const type = isCode ? "code" : "link";
131
+
92
132
  const result = await this.verificationController
93
133
  .validateVerificationCode({
94
- params: { type: "code" },
134
+ params: { type },
95
135
  body: { target: email, token },
96
136
  })
97
137
  .catch(() => {
98
- this.log.warn("Invalid email verification token", { email });
138
+ this.log.warn("Invalid email verification token", { email, type });
99
139
  throw new BadRequestError("Invalid or expired verification token");
100
140
  });
101
141
 
@@ -112,7 +152,7 @@ export class UserService {
112
152
  emailVerified: true,
113
153
  });
114
154
 
115
- this.log.info("Email verified", { email, userId: user.id });
155
+ this.log.info("Email verified", { email, userId: user.id, type });
116
156
  }
117
157
 
118
158
  /**
@@ -0,0 +1,15 @@
1
+ import { $module } from "alepha";
2
+
3
+ // ---------------------------------------------------------------------------------------------------------------------
4
+
5
+ export * from "./entities/verifications.ts";
6
+ export * from "./schemas/requestVerificationCodeResponseSchema.ts";
7
+ export * from "./schemas/validateVerificationCodeResponseSchema.ts";
8
+ export * from "./schemas/verificationTypeEnumSchema.ts";
9
+
10
+ // ---------------------------------------------------------------------------------------------------------------------
11
+
12
+ export const AlephaApiVerification = $module({
13
+ name: "alepha.api.verifications",
14
+ services: [],
15
+ });
@@ -6,6 +6,7 @@ import { VerificationService } from "./services/VerificationService.ts";
6
6
  // ---------------------------------------------------------------------------------------------------------------------
7
7
 
8
8
  export * from "./controllers/VerificationController.ts";
9
+ export * from "./entities/verifications.ts";
9
10
  export * from "./schemas/requestVerificationCodeResponseSchema.ts";
10
11
  export * from "./schemas/validateVerificationCodeResponseSchema.ts";
11
12
  export * from "./schemas/verificationTypeEnumSchema.ts";
@@ -213,13 +213,6 @@ export class BatchProvider {
213
213
  };
214
214
 
215
215
  // CAUTION: Do not log.debug/info here as it may cause infinite loops if logging is batched
216
- // log.trace is safe
217
-
218
- this.log.trace("Pushing item to batch", {
219
- id,
220
- partitionKey,
221
- item,
222
- });
223
216
 
224
217
  context.itemStates.set(id, itemState);
225
218
 
@@ -61,12 +61,14 @@ export const AlephaBucket = $module({
61
61
  MemoryFileStorageProvider,
62
62
  LocalFileStorageProvider,
63
63
  ],
64
- register: (alepha) =>
64
+ register: (alepha) => {
65
65
  alepha.with({
66
66
  optional: true,
67
67
  provide: FileStorageProvider,
68
- use: alepha.isTest()
69
- ? MemoryFileStorageProvider
70
- : LocalFileStorageProvider,
71
- }),
68
+ use:
69
+ alepha.isTest() || alepha.isServerless()
70
+ ? MemoryFileStorageProvider
71
+ : LocalFileStorageProvider,
72
+ });
73
+ },
72
74
  });
@@ -1,4 +1,6 @@
1
- import { $module } from "alepha";
1
+ import { join } from "node:path";
2
+ import { $hook, $inject, $module, Alepha } from "alepha";
3
+ import { FileSystemProvider } from "alepha/file";
2
4
  import { BiomeCommands } from "../commands/BiomeCommands.ts";
3
5
  import { CoreCommands } from "../commands/CoreCommands.ts";
4
6
  import { DrizzleCommands } from "../commands/DrizzleCommands.ts";
@@ -6,9 +8,33 @@ import { VerifyCommands } from "../commands/VerifyCommands.ts";
6
8
  import { ViteCommands } from "../commands/ViteCommands.ts";
7
9
  import { ProcessRunner } from "../services/ProcessRunner.ts";
8
10
 
11
+ class AlephaCliExtension {
12
+ protected readonly alepha = $inject(Alepha);
13
+ protected readonly fs = $inject(FileSystemProvider);
14
+ protected readonly onConfigure = $hook({
15
+ on: "configure",
16
+ handler: async () => {
17
+ const extensionPath = join(process.cwd(), "alepha.config.ts");
18
+ const hasExtension = await this.fs.exists(extensionPath);
19
+ if (!hasExtension) {
20
+ return;
21
+ }
22
+
23
+ // import
24
+ const { default: Extension } = await import(extensionPath);
25
+ if (typeof Extension !== "function") {
26
+ return;
27
+ }
28
+
29
+ this.alepha.with(Extension);
30
+ },
31
+ });
32
+ }
33
+
9
34
  export const AlephaCli = $module({
10
35
  name: "alepha.cli",
11
36
  services: [
37
+ AlephaCliExtension,
12
38
  ProcessRunner,
13
39
  CoreCommands,
14
40
  DrizzleCommands,
@@ -49,10 +49,13 @@ export class AlephaPackageBuilderCli {
49
49
  pkgData.exports[path]["react-native"] =
50
50
  `./src/${item.name}/index.browser.ts`;
51
51
  }
52
+
52
53
  if (item.browser) {
53
54
  pkgData.exports[path].browser = `./src/${item.name}/index.browser.ts`;
54
55
  }
56
+
55
57
  pkgData.exports[path].import = `./src/${item.name}/index.ts`;
58
+ pkgData.exports[path].default = `./src/${item.name}/index.ts`;
56
59
  }
57
60
 
58
61
  if (packageName === "alepha") {
@@ -62,11 +62,15 @@ export class CoreCommands {
62
62
  react: t.optional(
63
63
  t.boolean({ description: "Include Alepha React dependencies" }),
64
64
  ),
65
- admin: t.optional(
66
- t.boolean({ description: "Include Alepha admin panel dependencies" }),
65
+ ui: t.optional(
66
+ t.boolean({ description: "Include Alepha UI dependencies" }),
67
67
  ),
68
68
  }),
69
69
  handler: async ({ run, flags, root }) => {
70
+ if (flags.ui) {
71
+ flags.react = true;
72
+ }
73
+
70
74
  await run({
71
75
  name: "Ensuring configuration files",
72
76
  handler: async () => {
@@ -145,6 +145,7 @@ export class ViteCommands {
145
145
  ] ?? {};
146
146
 
147
147
  const stats = flags.stats ?? viteAlephaBuildOptions.stats ?? false;
148
+ const hasServer = viteAlephaBuildOptions.serverEntry !== false;
148
149
 
149
150
  let hasClient = false;
150
151
  try {
@@ -189,7 +190,7 @@ export class ViteCommands {
189
190
  });
190
191
 
191
192
  // Server will handle index.html if both client & server are built
192
- if (clientBuilt) {
193
+ if (clientBuilt && hasServer) {
193
194
  await unlink(`${distDir}/${clientDir}/index.html`);
194
195
  }
195
196
  },
@@ -72,6 +72,11 @@ export class ProjectUtils {
72
72
 
73
73
  const devDependencies: Record<string, string> = {};
74
74
 
75
+ if (modes.ui) {
76
+ dependencies["@alepha/ui"] = `^${version}`;
77
+ modes.react = true;
78
+ }
79
+
75
80
  if (modes.react) {
76
81
  dependencies["@alepha/react"] = `^${version}`;
77
82
  dependencies.react = "^19.2.0";
@@ -79,10 +84,6 @@ export class ProjectUtils {
79
84
  devDependencies["@types/react"] = "^19.2.0";
80
85
  }
81
86
 
82
- if (modes.admin) {
83
- dependencies["@alepha/ui"] = `^${version}`;
84
- }
85
-
86
87
  return {
87
88
  type: "module",
88
89
  dependencies,
@@ -174,33 +175,6 @@ export class ProjectUtils {
174
175
  await Promise.all(tasks);
175
176
  }
176
177
 
177
- /**
178
- * Ensure package.json exists and is configured as ES module.
179
- *
180
- * Similar to ensurePackageJson but only validates/sets the "type": "module" field.
181
- * Throws an error if no package.json exists.
182
- *
183
- * @param root - The root directory of the project
184
- * @throws {AlephaError} If no package.json is found
185
- */
186
- public async ensurePackageJsonModule(root: string): Promise<void> {
187
- const packageJsonPath = join(root, "package.json");
188
- try {
189
- await access(packageJsonPath);
190
- } catch (error) {
191
- throw new AlephaError(
192
- "No package.json found in project root. Run 'npx alepha init' to create one.",
193
- );
194
- }
195
-
196
- const content = await readFile(packageJsonPath, "utf8");
197
- const packageJson = JSON.parse(content);
198
- if (!packageJson.type || packageJson.type !== "module") {
199
- packageJson.type = "module";
200
- await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
201
- }
202
- }
203
-
204
178
  /**
205
179
  * Ensure tsconfig.json exists in the project.
206
180
  *
@@ -217,25 +191,30 @@ export class ProjectUtils {
217
191
  *
218
192
  * Creates a standard Alepha vite.config.ts if none exists.
219
193
  */
220
- public async ensureViteConfig(root: string): Promise<void> {
221
- await this.ensureFileExists(root, "vite.config.ts", viteConfigTs(), false);
194
+ public async ensureViteConfig(
195
+ root: string,
196
+ serverEntry?: string,
197
+ ): Promise<void> {
198
+ await this.ensureFileExists(
199
+ root,
200
+ "vite.config.ts",
201
+ viteConfigTs(serverEntry),
202
+ false,
203
+ );
222
204
  }
223
205
 
224
- protected async ensureFileExists(
206
+ protected async checkFileExists(
225
207
  root: string,
226
208
  name: string,
227
- content: string,
228
209
  checkParentDirectories: boolean = false,
229
- ): Promise<void> {
210
+ ): Promise<boolean> {
230
211
  const configPath = join(root, name);
231
-
232
212
  if (!checkParentDirectories) {
233
213
  try {
234
214
  await access(configPath);
235
- return;
215
+ return true;
236
216
  } catch {
237
- await writeFile(configPath, content);
238
- return;
217
+ return false;
239
218
  }
240
219
  }
241
220
 
@@ -259,8 +238,23 @@ export class ProjectUtils {
259
238
  level += 1;
260
239
  }
261
240
 
241
+ return found;
242
+ }
243
+
244
+ protected async ensureFileExists(
245
+ root: string,
246
+ name: string,
247
+ content: string,
248
+ checkParentDirectories: boolean = false,
249
+ ): Promise<void> {
250
+ const found = await this.checkFileExists(
251
+ root,
252
+ name,
253
+ checkParentDirectories,
254
+ );
255
+
262
256
  if (!found) {
263
- await writeFile(configPath, content);
257
+ await writeFile(join(root, name), content);
264
258
  }
265
259
  }
266
260
 
@@ -277,35 +271,6 @@ export class ProjectUtils {
277
271
  await this.ensureFileExists(root, "biome.json", biomeJson, true);
278
272
  }
279
273
 
280
- // ===================================================================================================================
281
- // Vite Configuration
282
- // ===================================================================================================================
283
-
284
- /**
285
- * Get the path to Vite configuration file.
286
- *
287
- * Looks for an existing vite.config.ts in the project root, or creates one if it doesn't exist.
288
- *
289
- * @param root - The root directory of the project (defaults to process.cwd())
290
- * @param serverEntry - Optional path to the server entry file to include in the config
291
- * @returns Absolute path to the vite.config.ts file
292
- */
293
- public async getViteConfigPath(
294
- root: string,
295
- serverEntry?: string,
296
- ): Promise<string> {
297
- try {
298
- const viteConfigPath = join(root, "vite.config.ts");
299
- await access(viteConfigPath);
300
- return viteConfigPath;
301
- } catch {
302
- return this.runner.writeConfigFile(
303
- "vite.config.ts",
304
- viteConfigTs(serverEntry),
305
- );
306
- }
307
- }
308
-
309
274
  // ===================================================================================================================
310
275
  // Drizzle ORM & Kit Utilities
311
276
  // ===================================================================================================================
@@ -544,13 +509,13 @@ ${models.map((it: string) => `export const ${it} = models["${it}"];`).join("\n")
544
509
  public async getPackageManager(
545
510
  root: string,
546
511
  ): Promise<"yarn" | "pnpm" | "npm"> {
547
- if (await this.fs.exists(join(root, "yarn.lock"))) {
512
+ if (await this.checkFileExists(root, "yarn.lock", true)) {
548
513
  return "yarn";
549
- } else if (await this.fs.exists(join(root, "pnpm-lock.yaml"))) {
514
+ }
515
+ if (await this.checkFileExists(root, "pnpm-lock.yaml", true)) {
550
516
  return "pnpm";
551
- } else {
552
- return "npm";
553
517
  }
518
+ return "npm";
554
519
  }
555
520
 
556
521
  public async ensureIndexHtml(root: string) {
@@ -597,5 +562,5 @@ ${models.map((it: string) => `export const ${it} = models["${it}"];`).join("\n")
597
562
 
598
563
  export interface DependencyModes {
599
564
  react?: boolean;
600
- admin?: boolean;
565
+ ui?: boolean;
601
566
  }
@@ -7,6 +7,7 @@ import {
7
7
  type Static,
8
8
  type TSchema,
9
9
  type TString,
10
+ t,
10
11
  } from "alepha";
11
12
  import { $logger } from "alepha/logger";
12
13
 
@@ -44,6 +45,8 @@ export interface AskMethod {
44
45
  question: string,
45
46
  options?: AskOptions<T>,
46
47
  ): Promise<Static<T>>;
48
+
49
+ permission: (question: string) => Promise<boolean>;
47
50
  }
48
51
 
49
52
  export class Asker {
@@ -63,6 +66,13 @@ export class Asker {
63
66
  return await this.prompt<T>(question, options);
64
67
  };
65
68
 
69
+ askFn.permission = async (question: string) => {
70
+ const response = await this.prompt(`${question} [Y/n]`, {
71
+ schema: t.enum(["Y", "y", "n", "no", "yes"], { default: "Y" }),
72
+ });
73
+ return response.charAt(0).toLowerCase() === "y";
74
+ };
75
+
66
76
  return askFn;
67
77
  }
68
78
 
@@ -68,11 +68,19 @@ export const AlephaEmail = $module({
68
68
  use: NodemailerEmailProvider,
69
69
  });
70
70
  } else {
71
- alepha.with({
72
- optional: true,
73
- provide: EmailProvider,
74
- use: LocalEmailProvider,
75
- });
71
+ if (alepha.isServerless()) {
72
+ alepha.with({
73
+ optional: true,
74
+ provide: EmailProvider,
75
+ use: MemoryEmailProvider,
76
+ });
77
+ } else {
78
+ alepha.with({
79
+ optional: true,
80
+ provide: EmailProvider,
81
+ use: LocalEmailProvider,
82
+ });
83
+ }
76
84
  }
77
85
  },
78
86
  });
@@ -82,10 +82,10 @@ export class NodeSqliteProvider extends DatabaseProvider {
82
82
  return path;
83
83
  }
84
84
 
85
- if (this.alepha.isTest()) {
85
+ if (this.alepha.isTest() || this.alepha.isServerless()) {
86
86
  return ":memory:";
87
87
  } else {
88
- return "node_modules/sqlite.db";
88
+ return "node_modules/.alepha/sqlite.db";
89
89
  }
90
90
  }
91
91
 
@@ -149,7 +149,7 @@ export class NodeSqliteProvider extends DatabaseProvider {
149
149
  if (filepath !== ":memory:" && filepath !== "") {
150
150
  const dirname = filepath.split("/").slice(0, -1).join("/");
151
151
  if (dirname) {
152
- await mkdir(dirname, { recursive: true });
152
+ await mkdir(dirname, { recursive: true }).catch(() => null);
153
153
  }
154
154
  }
155
155
 
@@ -11,6 +11,7 @@ import {
11
11
  $hook,
12
12
  $inject,
13
13
  Alepha,
14
+ AlephaError,
14
15
  type Static,
15
16
  type TSchema,
16
17
  t,
@@ -89,7 +90,7 @@ export class ServerCookiesProvider {
89
90
  this.alepha.context.get<ServerRequest>("request")?.cookies;
90
91
  if (cookies) return cookies;
91
92
  if (contextCookies) return contextCookies;
92
- throw new Error(
93
+ throw new AlephaError(
93
94
  "Cookie context is not available. This method must be called within a server request cycle.",
94
95
  );
95
96
  }
@@ -98,8 +98,15 @@ export class ServerStaticProvider {
98
98
  reply.headers["content-type"] = "text/html";
99
99
  reply.status = 200;
100
100
 
101
- // Serve index.html for all unmatched routes
102
- return createReadStream(join(root, "index.html"));
101
+ return new Promise<any>((resolve, reject) => {
102
+ const stream = createReadStream(join(root, "index.html"));
103
+ stream.on("open", () => {
104
+ resolve(stream);
105
+ });
106
+ stream.on("error", (err) => {
107
+ reject(err);
108
+ });
109
+ });
103
110
  },
104
111
  });
105
112
  }
@@ -161,7 +168,15 @@ export class ServerStaticProvider {
161
168
  return;
162
169
  }
163
170
 
164
- return createReadStream(path);
171
+ return new Promise<any>((resolve, reject) => {
172
+ const stream = createReadStream(path);
173
+ stream.on("open", () => {
174
+ resolve(stream);
175
+ });
176
+ stream.on("error", (err) => {
177
+ reject(err);
178
+ });
179
+ });
165
180
  };
166
181
  }
167
182