alepha 0.13.8 → 0.14.1

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 (160) hide show
  1. package/dist/api/audits/index.d.ts +418 -338
  2. package/dist/api/audits/index.d.ts.map +1 -0
  3. package/dist/api/files/index.d.ts +81 -1
  4. package/dist/api/files/index.d.ts.map +1 -0
  5. package/dist/api/jobs/index.d.ts +107 -27
  6. package/dist/api/jobs/index.d.ts.map +1 -0
  7. package/dist/api/notifications/index.d.ts +21 -1
  8. package/dist/api/notifications/index.d.ts.map +1 -0
  9. package/dist/api/parameters/index.d.ts +455 -8
  10. package/dist/api/parameters/index.d.ts.map +1 -0
  11. package/dist/api/users/index.d.ts +844 -840
  12. package/dist/api/users/index.d.ts.map +1 -0
  13. package/dist/api/verifications/index.d.ts.map +1 -0
  14. package/dist/batch/index.d.ts.map +1 -0
  15. package/dist/bucket/index.d.ts.map +1 -0
  16. package/dist/cache/core/index.d.ts.map +1 -0
  17. package/dist/cache/redis/index.d.ts.map +1 -0
  18. package/dist/cli/index.d.ts +254 -59
  19. package/dist/cli/index.d.ts.map +1 -0
  20. package/dist/cli/index.js +499 -127
  21. package/dist/cli/index.js.map +1 -1
  22. package/dist/command/index.d.ts +217 -10
  23. package/dist/command/index.d.ts.map +1 -0
  24. package/dist/command/index.js +350 -74
  25. package/dist/command/index.js.map +1 -1
  26. package/dist/core/index.browser.js +1334 -1318
  27. package/dist/core/index.browser.js.map +1 -1
  28. package/dist/core/index.d.ts +76 -72
  29. package/dist/core/index.d.ts.map +1 -0
  30. package/dist/core/index.js +1337 -1321
  31. package/dist/core/index.js.map +1 -1
  32. package/dist/core/index.native.js +1337 -1321
  33. package/dist/core/index.native.js.map +1 -1
  34. package/dist/datetime/index.d.ts.map +1 -0
  35. package/dist/email/index.d.ts.map +1 -0
  36. package/dist/fake/index.d.ts.map +1 -0
  37. package/dist/file/index.d.ts.map +1 -0
  38. package/dist/file/index.js.map +1 -1
  39. package/dist/lock/core/index.d.ts.map +1 -0
  40. package/dist/lock/redis/index.d.ts.map +1 -0
  41. package/dist/logger/index.d.ts +1 -0
  42. package/dist/logger/index.d.ts.map +1 -0
  43. package/dist/mcp/index.d.ts +820 -0
  44. package/dist/mcp/index.d.ts.map +1 -0
  45. package/dist/mcp/index.js +978 -0
  46. package/dist/mcp/index.js.map +1 -0
  47. package/dist/orm/index.d.ts +234 -107
  48. package/dist/orm/index.d.ts.map +1 -0
  49. package/dist/orm/index.js +376 -316
  50. package/dist/orm/index.js.map +1 -1
  51. package/dist/queue/core/index.d.ts +4 -4
  52. package/dist/queue/core/index.d.ts.map +1 -0
  53. package/dist/queue/redis/index.d.ts.map +1 -0
  54. package/dist/queue/redis/index.js +2 -4
  55. package/dist/queue/redis/index.js.map +1 -1
  56. package/dist/redis/index.d.ts +400 -29
  57. package/dist/redis/index.d.ts.map +1 -0
  58. package/dist/redis/index.js +412 -21
  59. package/dist/redis/index.js.map +1 -1
  60. package/dist/retry/index.d.ts.map +1 -0
  61. package/dist/router/index.d.ts.map +1 -0
  62. package/dist/scheduler/index.d.ts +6 -6
  63. package/dist/scheduler/index.d.ts.map +1 -0
  64. package/dist/security/index.d.ts +28 -28
  65. package/dist/security/index.d.ts.map +1 -0
  66. package/dist/server/auth/index.d.ts +155 -155
  67. package/dist/server/auth/index.d.ts.map +1 -0
  68. package/dist/server/cache/index.d.ts.map +1 -0
  69. package/dist/server/compress/index.d.ts.map +1 -0
  70. package/dist/server/cookies/index.d.ts.map +1 -0
  71. package/dist/server/core/index.d.ts +0 -1
  72. package/dist/server/core/index.d.ts.map +1 -0
  73. package/dist/server/core/index.js.map +1 -1
  74. package/dist/server/cors/index.d.ts.map +1 -0
  75. package/dist/server/health/index.d.ts +17 -17
  76. package/dist/server/health/index.d.ts.map +1 -0
  77. package/dist/server/helmet/index.d.ts +4 -1
  78. package/dist/server/helmet/index.d.ts.map +1 -0
  79. package/dist/server/links/index.d.ts +33 -33
  80. package/dist/server/links/index.d.ts.map +1 -0
  81. package/dist/server/metrics/index.d.ts.map +1 -0
  82. package/dist/server/multipart/index.d.ts.map +1 -0
  83. package/dist/server/multipart/index.js.map +1 -1
  84. package/dist/server/proxy/index.d.ts.map +1 -0
  85. package/dist/server/proxy/index.js.map +1 -1
  86. package/dist/server/rate-limit/index.d.ts.map +1 -0
  87. package/dist/server/security/index.d.ts +9 -9
  88. package/dist/server/security/index.d.ts.map +1 -0
  89. package/dist/server/static/index.d.ts.map +1 -0
  90. package/dist/server/swagger/index.d.ts.map +1 -0
  91. package/dist/sms/index.d.ts.map +1 -0
  92. package/dist/thread/index.d.ts.map +1 -0
  93. package/dist/topic/core/index.d.ts.map +1 -0
  94. package/dist/topic/redis/index.d.ts.map +1 -0
  95. package/dist/topic/redis/index.js +3 -3
  96. package/dist/topic/redis/index.js.map +1 -1
  97. package/dist/vite/index.d.ts +10 -2
  98. package/dist/vite/index.d.ts.map +1 -0
  99. package/dist/vite/index.js +45 -20
  100. package/dist/vite/index.js.map +1 -1
  101. package/dist/websocket/index.d.ts.map +1 -0
  102. package/package.json +9 -4
  103. package/src/cli/apps/AlephaCli.ts +10 -3
  104. package/src/cli/apps/AlephaPackageBuilderCli.ts +15 -8
  105. package/src/cli/assets/mainTs.ts +9 -10
  106. package/src/cli/atoms/changelogOptions.ts +45 -0
  107. package/src/cli/commands/ChangelogCommands.ts +259 -0
  108. package/src/cli/commands/DeployCommands.ts +118 -0
  109. package/src/cli/commands/DrizzleCommands.ts +230 -10
  110. package/src/cli/commands/ViteCommands.ts +47 -23
  111. package/src/cli/defineConfig.ts +15 -0
  112. package/src/cli/index.ts +3 -0
  113. package/src/cli/services/AlephaCliUtils.ts +10 -154
  114. package/src/cli/services/GitMessageParser.ts +77 -0
  115. package/src/command/helpers/EnvUtils.ts +37 -0
  116. package/src/command/index.ts +3 -1
  117. package/src/command/primitives/$command.ts +172 -6
  118. package/src/command/providers/CliProvider.ts +499 -95
  119. package/src/core/Alepha.ts +1 -1
  120. package/src/core/providers/SchemaValidator.ts +23 -1
  121. package/src/file/providers/NodeFileSystemProvider.ts +3 -1
  122. package/src/mcp/errors/McpError.ts +72 -0
  123. package/src/mcp/helpers/jsonrpc.ts +163 -0
  124. package/src/mcp/index.ts +132 -0
  125. package/src/mcp/interfaces/McpTypes.ts +248 -0
  126. package/src/mcp/primitives/$prompt.ts +188 -0
  127. package/src/mcp/primitives/$resource.ts +171 -0
  128. package/src/mcp/primitives/$tool.ts +285 -0
  129. package/src/mcp/providers/McpServerProvider.ts +382 -0
  130. package/src/mcp/transports/SseMcpTransport.ts +172 -0
  131. package/src/mcp/transports/StdioMcpTransport.ts +126 -0
  132. package/src/orm/index.ts +20 -4
  133. package/src/orm/interfaces/PgQueryWhere.ts +1 -26
  134. package/src/orm/providers/drivers/BunPostgresProvider.ts +225 -0
  135. package/src/orm/providers/drivers/BunSqliteProvider.ts +180 -0
  136. package/src/orm/providers/drivers/CloudflareD1Provider.ts +164 -0
  137. package/src/orm/providers/drivers/DatabaseProvider.ts +25 -0
  138. package/src/orm/providers/drivers/NodePostgresProvider.ts +0 -25
  139. package/src/orm/providers/drivers/NodeSqliteProvider.ts +3 -1
  140. package/src/orm/services/QueryManager.ts +10 -125
  141. package/src/queue/redis/providers/RedisQueueProvider.ts +2 -7
  142. package/src/redis/index.ts +65 -3
  143. package/src/redis/providers/BunRedisProvider.ts +304 -0
  144. package/src/redis/providers/BunRedisSubscriberProvider.ts +94 -0
  145. package/src/redis/providers/NodeRedisProvider.ts +280 -0
  146. package/src/redis/providers/NodeRedisSubscriberProvider.ts +94 -0
  147. package/src/redis/providers/RedisProvider.ts +134 -140
  148. package/src/redis/providers/RedisSubscriberProvider.ts +58 -49
  149. package/src/server/core/providers/BunHttpServerProvider.ts +0 -3
  150. package/src/server/core/providers/ServerBodyParserProvider.ts +3 -1
  151. package/src/server/core/providers/ServerProvider.ts +7 -4
  152. package/src/server/multipart/providers/ServerMultipartProvider.ts +3 -1
  153. package/src/server/proxy/providers/ServerProxyProvider.ts +1 -1
  154. package/src/topic/redis/providers/RedisTopicProvider.ts +3 -3
  155. package/src/vite/plugins/viteAlephaBuild.ts +8 -2
  156. package/src/vite/plugins/viteAlephaDev.ts +6 -2
  157. package/src/vite/tasks/buildServer.ts +2 -1
  158. package/src/vite/tasks/generateCloudflare.ts +43 -15
  159. package/src/vite/tasks/runAlepha.ts +1 -0
  160. package/src/orm/services/PgJsonQueryManager.ts +0 -511
package/src/orm/index.ts CHANGED
@@ -5,12 +5,14 @@ import { $entity } from "./primitives/$entity.ts";
5
5
  import { $repository } from "./primitives/$repository.ts";
6
6
  import { $sequence } from "./primitives/$sequence.ts";
7
7
  import { DrizzleKitProvider } from "./providers/DrizzleKitProvider.ts";
8
+ import { BunPostgresProvider } from "./providers/drivers/BunPostgresProvider.ts";
9
+ import { BunSqliteProvider } from "./providers/drivers/BunSqliteProvider.ts";
10
+ import { CloudflareD1Provider } from "./providers/drivers/CloudflareD1Provider.ts";
8
11
  import { DatabaseProvider } from "./providers/drivers/DatabaseProvider.ts";
9
12
  import { NodePostgresProvider } from "./providers/drivers/NodePostgresProvider.ts";
10
13
  import { NodeSqliteProvider } from "./providers/drivers/NodeSqliteProvider.ts";
11
14
  import { PglitePostgresProvider } from "./providers/drivers/PglitePostgresProvider.ts";
12
15
  import { RepositoryProvider } from "./providers/RepositoryProvider.ts";
13
- import { PgJsonQueryManager } from "./services/PgJsonQueryManager.ts";
14
16
  import { PgRelationManager } from "./services/PgRelationManager.ts";
15
17
  import { PostgresModelBuilder } from "./services/PostgresModelBuilder.ts";
16
18
  import { QueryManager } from "./services/QueryManager.ts";
@@ -113,6 +115,9 @@ export * from "./primitives/$repository.ts";
113
115
  export * from "./primitives/$sequence.ts";
114
116
  export * from "./primitives/$transaction.ts";
115
117
  export * from "./providers/DrizzleKitProvider.ts";
118
+ export * from "./providers/drivers/BunPostgresProvider.ts";
119
+ export * from "./providers/drivers/BunSqliteProvider.ts";
120
+ export * from "./providers/drivers/CloudflareD1Provider.ts";
116
121
  export * from "./providers/drivers/DatabaseProvider.ts";
117
122
  export * from "./providers/drivers/NodePostgresProvider.ts";
118
123
  export * from "./providers/drivers/NodeSqliteProvider.ts";
@@ -174,13 +179,15 @@ export const AlephaPostgres = $module({
174
179
  NodePostgresProvider,
175
180
  PglitePostgresProvider,
176
181
  NodeSqliteProvider,
182
+ BunPostgresProvider,
183
+ BunSqliteProvider,
184
+ CloudflareD1Provider,
177
185
  SqliteModelBuilder,
178
186
  PostgresModelBuilder,
179
187
  DrizzleKitProvider,
180
188
  RepositoryProvider,
181
189
  Repository,
182
190
  PgRelationManager,
183
- PgJsonQueryManager,
184
191
  QueryManager,
185
192
  ],
186
193
  register: (alepha: Alepha) => {
@@ -200,6 +207,15 @@ export const AlephaPostgres = $module({
200
207
  const isMemory = url?.includes(":memory:");
201
208
  const isFile = !!url && !isPostgres && !isMemory;
202
209
 
210
+ if (url?.startsWith("cloudflare-d1:")) {
211
+ alepha.with({
212
+ optional: true,
213
+ provide: DatabaseProvider,
214
+ use: CloudflareD1Provider,
215
+ });
216
+ return;
217
+ }
218
+
203
219
  if (hasPGlite && (isMemory || isFile || !url) && !isSqlite) {
204
220
  alepha.with({
205
221
  optional: true,
@@ -213,7 +229,7 @@ export const AlephaPostgres = $module({
213
229
  alepha.with({
214
230
  optional: true,
215
231
  provide: DatabaseProvider,
216
- use: NodePostgresProvider,
232
+ use: alepha.isBun() ? BunPostgresProvider : NodePostgresProvider,
217
233
  });
218
234
  return;
219
235
  }
@@ -221,7 +237,7 @@ export const AlephaPostgres = $module({
221
237
  alepha.with({
222
238
  optional: true,
223
239
  provide: DatabaseProvider,
224
- use: NodeSqliteProvider,
240
+ use: alepha.isBun() ? BunSqliteProvider : NodeSqliteProvider,
225
241
  });
226
242
  },
227
243
  });
@@ -20,12 +20,7 @@ export type PgQueryWhereOrSQL<
20
20
  // ---------------------------------------------------------------------------------------------------------------------
21
21
 
22
22
  type PgQueryWhereOperators<T extends TObject> = {
23
- [Key in keyof Static<T>]?:
24
- | FilterOperators<Static<T>[Key]>
25
- | Static<T>[Key]
26
- | (Static<T>[Key] extends object
27
- ? NestedJsonbQuery<Static<T>[Key]>
28
- : never);
23
+ [Key in keyof Static<T>]?: FilterOperators<Static<T>[Key]> | Static<T>[Key];
29
24
  };
30
25
 
31
26
  type PgQueryWhereConditions<
@@ -115,23 +110,3 @@ type PgQueryWhereRelations<
115
110
  >;
116
111
  }
117
112
  : {};
118
-
119
- /**
120
- * Recursively allow nested queries for JSONB object/array types
121
- */
122
- type NestedJsonbQuery<T> = T extends object
123
- ? T extends Array<infer U>
124
- ? // For arrays, allow querying array element properties
125
- U extends object
126
- ? {
127
- [K in keyof U]?: FilterOperators<U[K]> | U[K];
128
- }
129
- : FilterOperators<U> | U
130
- : // For objects, allow nested queries
131
- {
132
- [K in keyof T]?:
133
- | FilterOperators<T[K]>
134
- | T[K]
135
- | (T[K] extends object ? NestedJsonbQuery<T[K]> : never);
136
- }
137
- : FilterOperators<T> | T;
@@ -0,0 +1,225 @@
1
+ import { $env, $hook, $inject, AlephaError, type Static, t } from "alepha";
2
+ import { $lock } from "alepha/lock";
3
+ import { $logger } from "alepha/logger";
4
+ import type { SQL as BunSQL } from "bun";
5
+ import { sql } from "drizzle-orm";
6
+ import type { BunSQLDatabase } from "drizzle-orm/bun-sql";
7
+ import type { PgDatabase } from "drizzle-orm/pg-core";
8
+ import { DbError } from "../../errors/DbError.ts";
9
+ import { DbMigrationError } from "../../errors/DbMigrationError.ts";
10
+ import { PostgresModelBuilder } from "../../services/PostgresModelBuilder.ts";
11
+ import { DrizzleKitProvider } from "../DrizzleKitProvider.ts";
12
+ import { DatabaseProvider, type SQLLike } from "./DatabaseProvider.ts";
13
+
14
+ declare module "alepha" {
15
+ interface Env extends Partial<Static<typeof envSchema>> {}
16
+ }
17
+
18
+ const envSchema = t.object({
19
+ /**
20
+ * Main configuration for database connection.
21
+ * Accept a string in the format of a Postgres connection URL.
22
+ * Example: postgres://user:password@localhost:5432/database
23
+ * or
24
+ * Example: postgres://user:password@localhost:5432/database?sslmode=require
25
+ */
26
+ DATABASE_URL: t.optional(t.text()),
27
+
28
+ /**
29
+ * In addition to the DATABASE_URL, you can specify the postgres schema name.
30
+ */
31
+ POSTGRES_SCHEMA: t.optional(t.text()),
32
+ });
33
+
34
+ /**
35
+ * Bun PostgreSQL provider using Drizzle ORM with Bun's native SQL client.
36
+ *
37
+ * This provider uses Bun's built-in SQL class for PostgreSQL connections,
38
+ * which provides excellent performance on the Bun runtime.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * // Set DATABASE_URL environment variable
43
+ * // DATABASE_URL=postgres://user:password@localhost:5432/database
44
+ *
45
+ * // Or configure programmatically
46
+ * alepha.with({
47
+ * provide: DatabaseProvider,
48
+ * use: BunPostgresProvider,
49
+ * });
50
+ * ```
51
+ */
52
+ export class BunPostgresProvider extends DatabaseProvider {
53
+ protected readonly log = $logger();
54
+ protected readonly env = $env(envSchema);
55
+ protected readonly kit = $inject(DrizzleKitProvider);
56
+ protected readonly builder = $inject(PostgresModelBuilder);
57
+
58
+ protected client?: BunSQL;
59
+ protected bunDb?: BunSQLDatabase;
60
+
61
+ public readonly dialect = "postgresql";
62
+
63
+ public get name() {
64
+ return "bun-postgres";
65
+ }
66
+
67
+ /**
68
+ * In testing mode, the schema name will be generated and deleted after the test.
69
+ */
70
+ protected schemaForTesting = this.alepha.isTest()
71
+ ? this.env.POSTGRES_SCHEMA?.startsWith("test_")
72
+ ? this.env.POSTGRES_SCHEMA
73
+ : this.generateTestSchemaName()
74
+ : undefined;
75
+
76
+ public override get url() {
77
+ if (!this.env.DATABASE_URL) {
78
+ throw new AlephaError("DATABASE_URL is not defined in the environment");
79
+ }
80
+
81
+ return this.env.DATABASE_URL;
82
+ }
83
+
84
+ /**
85
+ * Execute a SQL statement.
86
+ */
87
+ public override execute(
88
+ statement: SQLLike,
89
+ ): Promise<Array<Record<string, unknown>>> {
90
+ try {
91
+ return this.db.execute(statement);
92
+ } catch (error) {
93
+ throw new DbError("Error executing statement", error);
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Get Postgres schema used by this provider.
99
+ */
100
+ public override get schema(): string {
101
+ if (this.schemaForTesting) {
102
+ return this.schemaForTesting;
103
+ }
104
+
105
+ if (this.env.POSTGRES_SCHEMA) {
106
+ return this.env.POSTGRES_SCHEMA;
107
+ }
108
+
109
+ return "public";
110
+ }
111
+
112
+ /**
113
+ * Get the Drizzle Postgres database instance.
114
+ */
115
+ public override get db(): PgDatabase<any> {
116
+ if (!this.bunDb) {
117
+ throw new AlephaError("Database not initialized");
118
+ }
119
+
120
+ return this.bunDb as unknown as PgDatabase<any>;
121
+ }
122
+
123
+ protected override async executeMigrations(
124
+ migrationsFolder: string,
125
+ ): Promise<void> {
126
+ const { migrate } = await import("drizzle-orm/bun-sql/migrator");
127
+ await migrate(this.bunDb!, { migrationsFolder });
128
+ }
129
+
130
+ // -------------------------------------------------------------------------------------------------------------------
131
+
132
+ protected readonly onStart = $hook({
133
+ on: "start",
134
+ handler: async () => {
135
+ await this.connect();
136
+
137
+ // never migrate in serverless mode (vercel, netlify, ...)
138
+ if (!this.alepha.isServerless()) {
139
+ try {
140
+ await this.migrate.run();
141
+ } catch (error) {
142
+ throw new DbMigrationError(error);
143
+ }
144
+ }
145
+ },
146
+ });
147
+
148
+ protected readonly onStop = $hook({
149
+ on: "stop",
150
+ handler: async () => {
151
+ // cleanup test schema
152
+ if (
153
+ this.alepha.isTest() &&
154
+ this.schemaForTesting &&
155
+ this.schemaForTesting.startsWith("test_")
156
+ ) {
157
+ // Additional validation: schema name must only contain safe characters
158
+ if (!/^test_[a-z0-9_]+$/i.test(this.schemaForTesting)) {
159
+ throw new AlephaError(
160
+ `Invalid test schema name: ${this.schemaForTesting}. Must match pattern: test_[a-z0-9_]+`,
161
+ );
162
+ }
163
+
164
+ this.log.warn(`Deleting test schema '${this.schemaForTesting}' ...`);
165
+ await this.execute(
166
+ sql`DROP SCHEMA IF EXISTS ${sql.raw(this.schemaForTesting)} CASCADE`,
167
+ );
168
+ this.log.info(`Test schema '${this.schemaForTesting}' deleted`);
169
+ }
170
+
171
+ // close the connection
172
+ await this.close();
173
+ },
174
+ });
175
+
176
+ public async connect(): Promise<void> {
177
+ this.log.debug("Connect ..");
178
+
179
+ // Check if we're running in Bun
180
+ if (typeof Bun === "undefined") {
181
+ throw new AlephaError(
182
+ "BunPostgresProvider requires the Bun runtime. Use NodePostgresProvider for Node.js.",
183
+ );
184
+ }
185
+
186
+ const { drizzle } = await import("drizzle-orm/bun-sql");
187
+ const { SQL } = await import("bun");
188
+
189
+ // Create Bun SQL client
190
+ this.client = new SQL(this.url);
191
+
192
+ // Test connection
193
+ await this.client.unsafe("SELECT 1");
194
+
195
+ this.bunDb = drizzle({
196
+ client: this.client,
197
+ logger: {
198
+ logQuery: (query: string, params: unknown[]) => {
199
+ this.log.trace(query, { params });
200
+ },
201
+ },
202
+ });
203
+
204
+ this.log.info("Connection OK");
205
+ }
206
+
207
+ public async close(): Promise<void> {
208
+ if (this.client) {
209
+ this.log.debug("Close...");
210
+
211
+ await this.client.close();
212
+
213
+ this.client = undefined;
214
+ this.bunDb = undefined;
215
+
216
+ this.log.info("Connection closed");
217
+ }
218
+ }
219
+
220
+ protected migrate = $lock({
221
+ handler: async () => {
222
+ await this.migrateDatabase();
223
+ },
224
+ });
225
+ }
@@ -0,0 +1,180 @@
1
+ import type { Database } from "bun:sqlite";
2
+ import { mkdir } from "node:fs/promises";
3
+ import {
4
+ $atom,
5
+ $env,
6
+ $hook,
7
+ $inject,
8
+ $use,
9
+ AlephaError,
10
+ type Static,
11
+ t,
12
+ } from "alepha";
13
+ import { $logger } from "alepha/logger";
14
+ import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite";
15
+ import type { PgDatabase } from "drizzle-orm/pg-core";
16
+ import { SqliteModelBuilder } from "../../services/SqliteModelBuilder.ts";
17
+ import { DrizzleKitProvider } from "../DrizzleKitProvider.ts";
18
+ import { DatabaseProvider, type SQLLike } from "./DatabaseProvider.ts";
19
+
20
+ // ---------------------------------------------------------------------------------------------------------------------
21
+
22
+ const envSchema = t.object({
23
+ DATABASE_URL: t.optional(t.text()),
24
+ });
25
+
26
+ /**
27
+ * Configuration options for the Bun SQLite database provider.
28
+ */
29
+ export const bunSqliteOptions = $atom({
30
+ name: "alepha.postgres.bun-sqlite.options",
31
+ schema: t.object({
32
+ path: t.optional(
33
+ t.string({
34
+ description:
35
+ "Filepath or :memory:. If empty, provider will use DATABASE_URL from env.",
36
+ }),
37
+ ),
38
+ }),
39
+ default: {},
40
+ });
41
+
42
+ export type BunSqliteProviderOptions = Static<typeof bunSqliteOptions.schema>;
43
+
44
+ declare module "alepha" {
45
+ interface State {
46
+ [bunSqliteOptions.key]: BunSqliteProviderOptions;
47
+ }
48
+ }
49
+
50
+ // ---------------------------------------------------------------------------------------------------------------------
51
+
52
+ /**
53
+ * Bun SQLite provider using Drizzle ORM with Bun's native SQLite client.
54
+ *
55
+ * This provider uses Bun's built-in `bun:sqlite` for SQLite connections,
56
+ * which provides excellent performance on the Bun runtime.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * // Set DATABASE_URL environment variable
61
+ * // DATABASE_URL=sqlite://./my-database.db
62
+ *
63
+ * // Or configure programmatically
64
+ * alepha.with({
65
+ * provide: DatabaseProvider,
66
+ * use: BunSqliteProvider,
67
+ * });
68
+ *
69
+ * // Or use options atom
70
+ * alepha.store.mut(bunSqliteOptions, (old) => ({
71
+ * ...old,
72
+ * path: ":memory:",
73
+ * }));
74
+ * ```
75
+ */
76
+ export class BunSqliteProvider extends DatabaseProvider {
77
+ protected readonly kit = $inject(DrizzleKitProvider);
78
+ protected readonly log = $logger();
79
+ protected readonly env = $env(envSchema);
80
+ protected readonly builder = $inject(SqliteModelBuilder);
81
+ protected readonly options = $use(bunSqliteOptions);
82
+
83
+ protected sqlite?: Database;
84
+ protected bunDb?: BunSQLiteDatabase;
85
+
86
+ public get name() {
87
+ return "bun-sqlite";
88
+ }
89
+
90
+ public override readonly dialect = "sqlite";
91
+
92
+ public override get url(): string {
93
+ const path = this.options.path ?? this.env.DATABASE_URL;
94
+ if (path) {
95
+ if (path.startsWith("postgres://")) {
96
+ throw new AlephaError(
97
+ "Postgres URL is not supported for SQLite provider.",
98
+ );
99
+ }
100
+ return path;
101
+ }
102
+
103
+ if (this.alepha.isTest() || this.alepha.isServerless()) {
104
+ return ":memory:";
105
+ } else {
106
+ return "node_modules/.alepha/bun-sqlite.db";
107
+ }
108
+ }
109
+
110
+ public override get db(): PgDatabase<any> {
111
+ if (!this.bunDb) {
112
+ throw new AlephaError("Database not initialized");
113
+ }
114
+
115
+ return this.bunDb as unknown as PgDatabase<any>;
116
+ }
117
+
118
+ public override async execute(
119
+ query: SQLLike,
120
+ ): Promise<Array<Record<string, unknown>>> {
121
+ return (this.bunDb as BunSQLiteDatabase).all(query);
122
+ }
123
+
124
+ protected readonly onStart = $hook({
125
+ on: "start",
126
+ handler: async () => {
127
+ // Check if we're running in Bun
128
+ if (typeof Bun === "undefined") {
129
+ throw new AlephaError(
130
+ "BunSqliteProvider requires the Bun runtime. Use NodeSqliteProvider for Node.js.",
131
+ );
132
+ }
133
+
134
+ const { Database } = await import("bun:sqlite");
135
+ const { drizzle } = await import("drizzle-orm/bun-sqlite");
136
+
137
+ const filepath = this.url.replace("sqlite://", "").replace("sqlite:", "");
138
+
139
+ if (filepath !== ":memory:" && filepath !== "") {
140
+ const dirname = filepath.split("/").slice(0, -1).join("/");
141
+ if (dirname) {
142
+ await mkdir(dirname, { recursive: true }).catch(() => null);
143
+ }
144
+ }
145
+
146
+ this.sqlite = new Database(filepath);
147
+
148
+ this.bunDb = drizzle({
149
+ client: this.sqlite,
150
+ logger: {
151
+ logQuery: (query: string, params: unknown[]) => {
152
+ this.log.trace(query, { params });
153
+ },
154
+ },
155
+ });
156
+
157
+ await this.migrateDatabase();
158
+
159
+ this.log.info(`Using Bun SQLite database at ${filepath}`);
160
+ },
161
+ });
162
+
163
+ protected readonly onStop = $hook({
164
+ on: "stop",
165
+ handler: async () => {
166
+ if (this.sqlite) {
167
+ this.log.debug("Closing Bun SQLite connection...");
168
+ this.sqlite.close();
169
+ this.sqlite = undefined;
170
+ this.bunDb = undefined;
171
+ this.log.info("Bun SQLite connection closed");
172
+ }
173
+ },
174
+ });
175
+
176
+ protected async executeMigrations(migrationsFolder: string): Promise<void> {
177
+ const { migrate } = await import("drizzle-orm/bun-sqlite/migrator");
178
+ await migrate(this.bunDb!, { migrationsFolder });
179
+ }
180
+ }
@@ -0,0 +1,164 @@
1
+ import { $env, $hook, $inject, AlephaError, t } from "alepha";
2
+ import { $logger } from "alepha/logger";
3
+ import type { DrizzleD1Database } from "drizzle-orm/d1";
4
+ import type { PgDatabase } from "drizzle-orm/pg-core";
5
+ import { SqliteModelBuilder } from "../../services/SqliteModelBuilder.ts";
6
+ import { DrizzleKitProvider } from "../DrizzleKitProvider.ts";
7
+ import { DatabaseProvider, type SQLLike } from "./DatabaseProvider.ts";
8
+
9
+ // ---------------------------------------------------------------------------------------------------------------------
10
+
11
+ /**
12
+ * D1Database interface matching Cloudflare's D1 API.
13
+ */
14
+ export interface D1Database {
15
+ prepare(query: string): D1PreparedStatement;
16
+ batch<T = unknown>(statements: D1PreparedStatement[]): Promise<T[]>;
17
+ exec(query: string): Promise<D1ExecResult>;
18
+ dump(): Promise<ArrayBuffer>;
19
+ }
20
+
21
+ export interface D1PreparedStatement {
22
+ bind(...values: unknown[]): D1PreparedStatement;
23
+ first<T = unknown>(colName?: string): Promise<T | null>;
24
+ run(): Promise<D1Result>;
25
+ all<T = unknown>(): Promise<D1Result<T>>;
26
+ raw<T = unknown>(): Promise<T[]>;
27
+ }
28
+
29
+ export interface D1Result<T = unknown> {
30
+ results: T[];
31
+ success: boolean;
32
+ meta: {
33
+ duration: number;
34
+ changes: number;
35
+ last_row_id: number;
36
+ served_by: string;
37
+ internal_stats: unknown;
38
+ };
39
+ }
40
+
41
+ export interface D1ExecResult {
42
+ count: number;
43
+ duration: number;
44
+ }
45
+
46
+ // ---------------------------------------------------------------------------------------------------------------------
47
+
48
+ /**
49
+ * Cloudflare D1 SQLite provider using Drizzle ORM.
50
+ *
51
+ * This provider requires a D1 binding to be set via `cloudflareD1Options` before starting.
52
+ * The binding is typically obtained from the Cloudflare Workers environment.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * // In your Cloudflare Worker
57
+ * alepha.set(cloudflareD1Options, { binding: env.DB });
58
+ * ```
59
+ */
60
+ export class CloudflareD1Provider extends DatabaseProvider {
61
+ protected readonly kit = $inject(DrizzleKitProvider);
62
+ protected readonly log = $logger();
63
+ protected readonly builder = $inject(SqliteModelBuilder);
64
+ protected readonly env = $env(
65
+ t.object({
66
+ DATABASE_URL: t.string({
67
+ description: "Expect to be 'cloudflare-d1://name:id'",
68
+ }),
69
+ }),
70
+ );
71
+
72
+ protected d1?: D1Database;
73
+ protected drizzleDb?: DrizzleD1Database;
74
+
75
+ public get name() {
76
+ return "d1";
77
+ }
78
+
79
+ public override readonly dialect = "sqlite";
80
+
81
+ public override get url(): string {
82
+ return this.env.DATABASE_URL;
83
+ }
84
+
85
+ public override get db(): PgDatabase<any> {
86
+ if (!this.drizzleDb) {
87
+ throw new AlephaError("D1 database not initialized");
88
+ }
89
+
90
+ return this.drizzleDb as unknown as PgDatabase<any>;
91
+ }
92
+
93
+ public override async execute(
94
+ query: SQLLike,
95
+ ): Promise<Array<Record<string, unknown>>> {
96
+ const { rows } = await (this.db as any).run(query);
97
+ return rows;
98
+ }
99
+
100
+ protected readonly onStart = $hook({
101
+ on: "start",
102
+ handler: async () => {
103
+ const [bindingName] = this.env.DATABASE_URL.replace(
104
+ "cloudflare-d1://",
105
+ "",
106
+ ).split(":");
107
+ const cloudflareEnv = this.alepha.store.get("cloudflare.env" as any);
108
+ if (!cloudflareEnv) {
109
+ throw new AlephaError(
110
+ "Cloudflare Workers environment not found in Alepha store under 'cloudflare.env'.",
111
+ );
112
+ }
113
+
114
+ const binding = cloudflareEnv[bindingName] as D1Database;
115
+ if (!binding) {
116
+ throw new AlephaError(
117
+ `D1 binding '${bindingName}' not found in Cloudflare Workers environment.`,
118
+ );
119
+ }
120
+
121
+ this.d1 = binding;
122
+
123
+ // Dynamic import to avoid crashes when not on Cloudflare
124
+ const { drizzle } = await import("drizzle-orm/d1");
125
+
126
+ this.drizzleDb = drizzle(this.d1) as DrizzleD1Database;
127
+
128
+ await this.migrateDatabase();
129
+
130
+ this.log.info("Using Cloudflare D1 database");
131
+ },
132
+ });
133
+
134
+ protected async executeMigrations(migrationsFolder: string): Promise<void> {
135
+ // Dynamic import for D1 migrator
136
+ const { migrate } = await import("drizzle-orm/d1/migrator");
137
+ await migrate(this.db as any, { migrationsFolder });
138
+ }
139
+
140
+ /**
141
+ * Override development migration to skip sync (not supported on D1).
142
+ * D1 requires proper migrations to be applied.
143
+ */
144
+ protected override async runDevelopmentMigration(
145
+ migrationsFolder: string,
146
+ ): Promise<void> {
147
+ await this.executeMigrations(migrationsFolder);
148
+ }
149
+
150
+ /**
151
+ * Override test migration to run migrations instead of sync.
152
+ * D1 doesn't support schema synchronization.
153
+ */
154
+ protected override async runTestMigration(): Promise<void> {
155
+ const migrationsFolder = this.getMigrationsFolder();
156
+ try {
157
+ await this.executeMigrations(migrationsFolder);
158
+ } catch {
159
+ this.log.warn(
160
+ "D1 migrations failed in test environment - ensure migrations exist",
161
+ );
162
+ }
163
+ }
164
+ }