alepha 0.14.0 → 0.14.2

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 (149) hide show
  1. package/README.md +3 -3
  2. package/dist/api/audits/index.d.ts +80 -1
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/audits/index.js.map +1 -1
  5. package/dist/api/files/index.d.ts +80 -1
  6. package/dist/api/files/index.d.ts.map +1 -1
  7. package/dist/api/files/index.js.map +1 -1
  8. package/dist/api/jobs/index.d.ts +236 -157
  9. package/dist/api/jobs/index.d.ts.map +1 -1
  10. package/dist/api/jobs/index.js.map +1 -1
  11. package/dist/api/notifications/index.d.ts +21 -1
  12. package/dist/api/notifications/index.d.ts.map +1 -1
  13. package/dist/api/parameters/index.d.ts +451 -4
  14. package/dist/api/parameters/index.d.ts.map +1 -1
  15. package/dist/api/parameters/index.js.map +1 -1
  16. package/dist/api/users/index.d.ts +252 -249
  17. package/dist/api/users/index.d.ts.map +1 -1
  18. package/dist/api/users/index.js +4 -0
  19. package/dist/api/users/index.js.map +1 -1
  20. package/dist/api/verifications/index.d.ts +128 -128
  21. package/dist/api/verifications/index.d.ts.map +1 -1
  22. package/dist/batch/index.js.map +1 -1
  23. package/dist/cache/core/index.js.map +1 -1
  24. package/dist/cli/index.d.ts +304 -115
  25. package/dist/cli/index.d.ts.map +1 -1
  26. package/dist/cli/index.js +650 -531
  27. package/dist/cli/index.js.map +1 -1
  28. package/dist/command/index.d.ts +210 -13
  29. package/dist/command/index.d.ts.map +1 -1
  30. package/dist/command/index.js +306 -69
  31. package/dist/command/index.js.map +1 -1
  32. package/dist/core/index.browser.js.map +1 -1
  33. package/dist/core/index.d.ts +1 -1
  34. package/dist/core/index.d.ts.map +1 -1
  35. package/dist/core/index.js +7 -6
  36. package/dist/core/index.js.map +1 -1
  37. package/dist/core/index.native.js +7 -6
  38. package/dist/core/index.native.js.map +1 -1
  39. package/dist/datetime/index.js.map +1 -1
  40. package/dist/fake/index.js.map +1 -1
  41. package/dist/file/index.d.ts.map +1 -1
  42. package/dist/file/index.js.map +1 -1
  43. package/dist/lock/redis/index.js.map +1 -1
  44. package/dist/logger/index.js.map +1 -1
  45. package/dist/mcp/index.js.map +1 -1
  46. package/dist/orm/index.browser.js +26 -5
  47. package/dist/orm/index.browser.js.map +1 -1
  48. package/dist/orm/index.d.ts +294 -215
  49. package/dist/orm/index.d.ts.map +1 -1
  50. package/dist/orm/index.js +522 -523
  51. package/dist/orm/index.js.map +1 -1
  52. package/dist/queue/redis/index.js +2 -4
  53. package/dist/queue/redis/index.js.map +1 -1
  54. package/dist/redis/index.d.ts +400 -29
  55. package/dist/redis/index.d.ts.map +1 -1
  56. package/dist/redis/index.js +412 -21
  57. package/dist/redis/index.js.map +1 -1
  58. package/dist/retry/index.js.map +1 -1
  59. package/dist/router/index.js.map +1 -1
  60. package/dist/scheduler/index.js.map +1 -1
  61. package/dist/security/index.d.ts.map +1 -1
  62. package/dist/security/index.js.map +1 -1
  63. package/dist/server/auth/index.d.ts +155 -155
  64. package/dist/server/auth/index.js.map +1 -1
  65. package/dist/server/cache/index.js.map +1 -1
  66. package/dist/server/cookies/index.browser.js.map +1 -1
  67. package/dist/server/cookies/index.js.map +1 -1
  68. package/dist/server/core/index.browser.js.map +1 -1
  69. package/dist/server/core/index.d.ts +0 -1
  70. package/dist/server/core/index.d.ts.map +1 -1
  71. package/dist/server/core/index.js.map +1 -1
  72. package/dist/server/helmet/index.d.ts +4 -1
  73. package/dist/server/helmet/index.d.ts.map +1 -1
  74. package/dist/server/helmet/index.js.map +1 -1
  75. package/dist/server/links/index.browser.js.map +1 -1
  76. package/dist/server/links/index.js.map +1 -1
  77. package/dist/server/multipart/index.d.ts.map +1 -1
  78. package/dist/server/multipart/index.js.map +1 -1
  79. package/dist/server/proxy/index.js.map +1 -1
  80. package/dist/server/rate-limit/index.js.map +1 -1
  81. package/dist/server/security/index.d.ts +9 -9
  82. package/dist/server/security/index.js.map +1 -1
  83. package/dist/server/swagger/index.js.map +1 -1
  84. package/dist/thread/index.js.map +1 -1
  85. package/dist/topic/core/index.js.map +1 -1
  86. package/dist/topic/redis/index.js +3 -3
  87. package/dist/topic/redis/index.js.map +1 -1
  88. package/dist/vite/index.js +9 -6
  89. package/dist/vite/index.js.map +1 -1
  90. package/dist/websocket/index.browser.js.map +1 -1
  91. package/dist/websocket/index.d.ts +7 -7
  92. package/dist/websocket/index.js.map +1 -1
  93. package/package.json +3 -3
  94. package/src/api/users/index.ts +4 -0
  95. package/src/cli/apps/AlephaCli.ts +36 -14
  96. package/src/cli/apps/AlephaPackageBuilderCli.ts +5 -1
  97. package/src/cli/assets/appRouterTs.ts +1 -1
  98. package/src/cli/atoms/changelogOptions.ts +45 -0
  99. package/src/cli/commands/{ViteCommands.ts → build.ts} +4 -93
  100. package/src/cli/commands/changelog.ts +244 -0
  101. package/src/cli/commands/clean.ts +14 -0
  102. package/src/cli/commands/{DrizzleCommands.ts → db.ts} +37 -124
  103. package/src/cli/commands/deploy.ts +118 -0
  104. package/src/cli/commands/dev.ts +57 -0
  105. package/src/cli/commands/format.ts +17 -0
  106. package/src/cli/commands/{CoreCommands.ts → init.ts} +2 -40
  107. package/src/cli/commands/lint.ts +17 -0
  108. package/src/cli/commands/root.ts +32 -0
  109. package/src/cli/commands/run.ts +24 -0
  110. package/src/cli/commands/test.ts +42 -0
  111. package/src/cli/commands/typecheck.ts +19 -0
  112. package/src/cli/commands/{VerifyCommands.ts → verify.ts} +1 -13
  113. package/src/cli/defineConfig.ts +24 -0
  114. package/src/cli/index.ts +17 -5
  115. package/src/cli/services/AlephaCliUtils.ts +4 -21
  116. package/src/cli/services/GitMessageParser.ts +77 -0
  117. package/src/command/helpers/EnvUtils.ts +37 -0
  118. package/src/command/index.ts +3 -1
  119. package/src/command/primitives/$command.ts +172 -6
  120. package/src/command/providers/CliProvider.ts +424 -91
  121. package/src/core/Alepha.ts +8 -5
  122. package/src/file/providers/NodeFileSystemProvider.ts +3 -1
  123. package/src/orm/index.browser.ts +1 -1
  124. package/src/orm/index.ts +18 -10
  125. package/src/orm/interfaces/PgQueryWhere.ts +1 -26
  126. package/src/orm/providers/{PostgresTypeProvider.ts → DatabaseTypeProvider.ts} +25 -3
  127. package/src/orm/providers/drivers/BunPostgresProvider.ts +225 -0
  128. package/src/orm/providers/drivers/BunSqliteProvider.ts +180 -0
  129. package/src/orm/providers/drivers/DatabaseProvider.ts +25 -0
  130. package/src/orm/providers/drivers/NodePostgresProvider.ts +0 -25
  131. package/src/orm/services/QueryManager.ts +10 -125
  132. package/src/queue/redis/providers/RedisQueueProvider.ts +2 -7
  133. package/src/redis/index.ts +65 -3
  134. package/src/redis/providers/BunRedisProvider.ts +304 -0
  135. package/src/redis/providers/BunRedisSubscriberProvider.ts +94 -0
  136. package/src/redis/providers/NodeRedisProvider.ts +280 -0
  137. package/src/redis/providers/NodeRedisSubscriberProvider.ts +94 -0
  138. package/src/redis/providers/RedisProvider.ts +134 -140
  139. package/src/redis/providers/RedisSubscriberProvider.ts +58 -49
  140. package/src/server/core/providers/BunHttpServerProvider.ts +0 -3
  141. package/src/server/core/providers/ServerBodyParserProvider.ts +3 -1
  142. package/src/server/core/providers/ServerProvider.ts +7 -4
  143. package/src/server/multipart/providers/ServerMultipartProvider.ts +3 -1
  144. package/src/server/proxy/providers/ServerProxyProvider.ts +1 -1
  145. package/src/topic/redis/providers/RedisTopicProvider.ts +3 -3
  146. package/src/vite/tasks/buildServer.ts +1 -0
  147. package/src/cli/commands/BiomeCommands.ts +0 -29
  148. package/src/cli/commands/ChangelogCommands.ts +0 -389
  149. package/src/orm/services/PgJsonQueryManager.ts +0 -511
package/src/orm/index.ts CHANGED
@@ -5,13 +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";
8
10
  import { CloudflareD1Provider } from "./providers/drivers/CloudflareD1Provider.ts";
9
11
  import { DatabaseProvider } from "./providers/drivers/DatabaseProvider.ts";
10
12
  import { NodePostgresProvider } from "./providers/drivers/NodePostgresProvider.ts";
11
13
  import { NodeSqliteProvider } from "./providers/drivers/NodeSqliteProvider.ts";
12
14
  import { PglitePostgresProvider } from "./providers/drivers/PglitePostgresProvider.ts";
13
15
  import { RepositoryProvider } from "./providers/RepositoryProvider.ts";
14
- import { PgJsonQueryManager } from "./services/PgJsonQueryManager.ts";
15
16
  import { PgRelationManager } from "./services/PgRelationManager.ts";
16
17
  import { PostgresModelBuilder } from "./services/PostgresModelBuilder.ts";
17
18
  import { QueryManager } from "./services/QueryManager.ts";
@@ -113,12 +114,14 @@ export * from "./primitives/$entity.ts";
113
114
  export * from "./primitives/$repository.ts";
114
115
  export * from "./primitives/$sequence.ts";
115
116
  export * from "./primitives/$transaction.ts";
117
+ export * from "./providers/DatabaseTypeProvider.ts";
116
118
  export * from "./providers/DrizzleKitProvider.ts";
119
+ export * from "./providers/drivers/BunPostgresProvider.ts";
120
+ export * from "./providers/drivers/BunSqliteProvider.ts";
117
121
  export * from "./providers/drivers/CloudflareD1Provider.ts";
118
122
  export * from "./providers/drivers/DatabaseProvider.ts";
119
123
  export * from "./providers/drivers/NodePostgresProvider.ts";
120
124
  export * from "./providers/drivers/NodeSqliteProvider.ts";
121
- export * from "./providers/PostgresTypeProvider.ts";
122
125
  export * from "./providers/RepositoryProvider.ts";
123
126
  export * from "./schemas/insertSchema.ts";
124
127
  export * from "./schemas/legacyIdSchema.ts";
@@ -132,21 +135,25 @@ export * from "./types/schema.ts";
132
135
  * Postgres client based on Drizzle ORM, Alepha type-safe friendly.
133
136
  *
134
137
  * ```ts
138
+ * import { t } from "alepha";
139
+ * import { $entity, $repository, db } from "alepha/postgres";
140
+ *
135
141
  * const users = $entity({
136
142
  * name: "users",
137
143
  * schema: t.object({
138
- * id: pg.primaryKey(),
144
+ * id: db.primaryKey(),
139
145
  * name: t.text(),
140
146
  * email: t.text(),
141
147
  * }),
142
148
  * });
143
149
  *
144
- * class Db {
150
+ * class App {
145
151
  * users = $repository(users);
146
- * }
147
152
  *
148
- * const db = alepha.inject(Db);
149
- * const user = await db.users.one({ name: { eq: "John Doe" } });
153
+ * getUserByName(name: string) {
154
+ * return this.users.findOne({ name: { eq: name } });
155
+ * }
156
+ * }
150
157
  * ```
151
158
  *
152
159
  * This is not a full ORM, but rather a set of tools to work with Postgres databases in a type-safe way.
@@ -176,6 +183,8 @@ export const AlephaPostgres = $module({
176
183
  NodePostgresProvider,
177
184
  PglitePostgresProvider,
178
185
  NodeSqliteProvider,
186
+ BunPostgresProvider,
187
+ BunSqliteProvider,
179
188
  CloudflareD1Provider,
180
189
  SqliteModelBuilder,
181
190
  PostgresModelBuilder,
@@ -183,7 +192,6 @@ export const AlephaPostgres = $module({
183
192
  RepositoryProvider,
184
193
  Repository,
185
194
  PgRelationManager,
186
- PgJsonQueryManager,
187
195
  QueryManager,
188
196
  ],
189
197
  register: (alepha: Alepha) => {
@@ -225,7 +233,7 @@ export const AlephaPostgres = $module({
225
233
  alepha.with({
226
234
  optional: true,
227
235
  provide: DatabaseProvider,
228
- use: NodePostgresProvider,
236
+ use: alepha.isBun() ? BunPostgresProvider : NodePostgresProvider,
229
237
  });
230
238
  return;
231
239
  }
@@ -233,7 +241,7 @@ export const AlephaPostgres = $module({
233
241
  alepha.with({
234
242
  optional: true,
235
243
  provide: DatabaseProvider,
236
- use: NodeSqliteProvider,
244
+ use: alepha.isBun() ? BunSqliteProvider : NodeSqliteProvider,
237
245
  });
238
246
  },
239
247
  });
@@ -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;
@@ -35,7 +35,7 @@ import {
35
35
  import type { PgAttr } from "../helpers/pgAttr.ts";
36
36
  import { pgAttr } from "../helpers/pgAttr.ts";
37
37
 
38
- export class PostgresTypeProvider {
38
+ export class DatabaseTypeProvider {
39
39
  public readonly attr = pgAttr;
40
40
 
41
41
  /**
@@ -239,7 +239,7 @@ export class PostgresTypeProvider {
239
239
 
240
240
  /**
241
241
  * Creates a page schema for a given object schema.
242
- * It's used by {@link RepositoryPrimitive#paginate} method.
242
+ * It's used by {@link Repository#paginate} method.
243
243
  */
244
244
  public readonly page = <T extends TObject>(
245
245
  resource: T,
@@ -249,4 +249,26 @@ export class PostgresTypeProvider {
249
249
  };
250
250
  }
251
251
 
252
- export const pg = new PostgresTypeProvider();
252
+ /**
253
+ * Wrapper of TypeProvider (`t`) for database types.
254
+ *
255
+ * Use `db` for improve TypeBox schema definitions with database-specific attributes.
256
+ *
257
+ * @example
258
+ * ```ts
259
+ * import { t } from "alepha";
260
+ * import { db } from "alepha/orm";
261
+ *
262
+ * const userSchema = t.object({
263
+ * id: db.primaryKey(t.uuid()),
264
+ * email: t.email(),
265
+ * createdAt: db.createdAt(),
266
+ * });
267
+ * ```
268
+ */
269
+ export const db = new DatabaseTypeProvider();
270
+
271
+ /**
272
+ * @deprecated Use `db` instead.
273
+ */
274
+ export const pg = db;
@@ -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
+ }
@@ -176,4 +176,29 @@ export abstract class DatabaseProvider {
176
176
  * MUST be implemented by each provider
177
177
  */
178
178
  protected abstract executeMigrations(migrationsFolder: string): Promise<void>;
179
+
180
+ // -------------------------------------------------------------------------------------------------------------------
181
+
182
+ /**
183
+ * For testing purposes, generate a unique schema name.
184
+ * The schema name will be generated based on the current date and time.
185
+ * It will be in the format of `test_YYYYMMDD_HHMMSS_randomSuffix`.
186
+ */
187
+ protected generateTestSchemaName(): string {
188
+ const pad = (n: number) => n.toString().padStart(2, "0");
189
+
190
+ const now = new Date();
191
+ const year = now.getUTCFullYear();
192
+ const month = pad(now.getUTCMonth() + 1);
193
+ const day = pad(now.getUTCDate());
194
+ const hours = pad(now.getUTCHours());
195
+ const minutes = pad(now.getUTCMinutes());
196
+ const seconds = pad(now.getUTCSeconds());
197
+
198
+ const timestamp = `${year}${month}${day}_${hours}${minutes}${seconds}`;
199
+
200
+ const randomSuffix = Math.random().toString(36).slice(2, 6); // 4 alphanumeric chars
201
+
202
+ return `test_${timestamp}_${randomSuffix}`;
203
+ }
179
204
  }
@@ -231,29 +231,4 @@ export class NodePostgresProvider extends DatabaseProvider {
231
231
  }
232
232
  }
233
233
  }
234
-
235
- // -------------------------------------------------------------------------------------------------------------------
236
-
237
- /**
238
- * For testing purposes, generate a unique schema name.
239
- * The schema name will be generated based on the current date and time.
240
- * It will be in the format of `test_YYYYMMDD_HHMMSS_randomSuffix`.
241
- */
242
- protected generateTestSchemaName(): string {
243
- const pad = (n: number) => n.toString().padStart(2, "0");
244
-
245
- const now = new Date();
246
- const year = now.getUTCFullYear();
247
- const month = pad(now.getUTCMonth() + 1);
248
- const day = pad(now.getUTCDate());
249
- const hours = pad(now.getUTCHours());
250
- const minutes = pad(now.getUTCMinutes());
251
- const seconds = pad(now.getUTCSeconds());
252
-
253
- const timestamp = `${year}${month}${day}_${hours}${minutes}${seconds}`;
254
-
255
- const randomSuffix = Math.random().toString(36).slice(2, 6); // 4 alphanumeric chars
256
-
257
- return `test_${timestamp}_${randomSuffix}`;
258
- }
259
234
  }