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.
- package/dist/api/audits/index.d.ts +418 -338
- package/dist/api/audits/index.d.ts.map +1 -0
- package/dist/api/files/index.d.ts +81 -1
- package/dist/api/files/index.d.ts.map +1 -0
- package/dist/api/jobs/index.d.ts +107 -27
- package/dist/api/jobs/index.d.ts.map +1 -0
- package/dist/api/notifications/index.d.ts +21 -1
- package/dist/api/notifications/index.d.ts.map +1 -0
- package/dist/api/parameters/index.d.ts +455 -8
- package/dist/api/parameters/index.d.ts.map +1 -0
- package/dist/api/users/index.d.ts +844 -840
- package/dist/api/users/index.d.ts.map +1 -0
- package/dist/api/verifications/index.d.ts.map +1 -0
- package/dist/batch/index.d.ts.map +1 -0
- package/dist/bucket/index.d.ts.map +1 -0
- package/dist/cache/core/index.d.ts.map +1 -0
- package/dist/cache/redis/index.d.ts.map +1 -0
- package/dist/cli/index.d.ts +254 -59
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +499 -127
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +217 -10
- package/dist/command/index.d.ts.map +1 -0
- package/dist/command/index.js +350 -74
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +1334 -1318
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +76 -72
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +1337 -1321
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +1337 -1321
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.d.ts.map +1 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/fake/index.d.ts.map +1 -0
- package/dist/file/index.d.ts.map +1 -0
- package/dist/file/index.js.map +1 -1
- package/dist/lock/core/index.d.ts.map +1 -0
- package/dist/lock/redis/index.d.ts.map +1 -0
- package/dist/logger/index.d.ts +1 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/mcp/index.d.ts +820 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +978 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/orm/index.d.ts +234 -107
- package/dist/orm/index.d.ts.map +1 -0
- package/dist/orm/index.js +376 -316
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +4 -4
- package/dist/queue/core/index.d.ts.map +1 -0
- package/dist/queue/redis/index.d.ts.map +1 -0
- package/dist/queue/redis/index.js +2 -4
- package/dist/queue/redis/index.js.map +1 -1
- package/dist/redis/index.d.ts +400 -29
- package/dist/redis/index.d.ts.map +1 -0
- package/dist/redis/index.js +412 -21
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.d.ts.map +1 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/scheduler/index.d.ts +6 -6
- package/dist/scheduler/index.d.ts.map +1 -0
- package/dist/security/index.d.ts +28 -28
- package/dist/security/index.d.ts.map +1 -0
- package/dist/server/auth/index.d.ts +155 -155
- package/dist/server/auth/index.d.ts.map +1 -0
- package/dist/server/cache/index.d.ts.map +1 -0
- package/dist/server/compress/index.d.ts.map +1 -0
- package/dist/server/cookies/index.d.ts.map +1 -0
- package/dist/server/core/index.d.ts +0 -1
- package/dist/server/core/index.d.ts.map +1 -0
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts.map +1 -0
- package/dist/server/health/index.d.ts +17 -17
- package/dist/server/health/index.d.ts.map +1 -0
- package/dist/server/helmet/index.d.ts +4 -1
- package/dist/server/helmet/index.d.ts.map +1 -0
- package/dist/server/links/index.d.ts +33 -33
- package/dist/server/links/index.d.ts.map +1 -0
- package/dist/server/metrics/index.d.ts.map +1 -0
- package/dist/server/multipart/index.d.ts.map +1 -0
- package/dist/server/multipart/index.js.map +1 -1
- package/dist/server/proxy/index.d.ts.map +1 -0
- package/dist/server/proxy/index.js.map +1 -1
- package/dist/server/rate-limit/index.d.ts.map +1 -0
- package/dist/server/security/index.d.ts +9 -9
- package/dist/server/security/index.d.ts.map +1 -0
- package/dist/server/static/index.d.ts.map +1 -0
- package/dist/server/swagger/index.d.ts.map +1 -0
- package/dist/sms/index.d.ts.map +1 -0
- package/dist/thread/index.d.ts.map +1 -0
- package/dist/topic/core/index.d.ts.map +1 -0
- package/dist/topic/redis/index.d.ts.map +1 -0
- package/dist/topic/redis/index.js +3 -3
- package/dist/topic/redis/index.js.map +1 -1
- package/dist/vite/index.d.ts +10 -2
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +45 -20
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.d.ts.map +1 -0
- package/package.json +9 -4
- package/src/cli/apps/AlephaCli.ts +10 -3
- package/src/cli/apps/AlephaPackageBuilderCli.ts +15 -8
- package/src/cli/assets/mainTs.ts +9 -10
- package/src/cli/atoms/changelogOptions.ts +45 -0
- package/src/cli/commands/ChangelogCommands.ts +259 -0
- package/src/cli/commands/DeployCommands.ts +118 -0
- package/src/cli/commands/DrizzleCommands.ts +230 -10
- package/src/cli/commands/ViteCommands.ts +47 -23
- package/src/cli/defineConfig.ts +15 -0
- package/src/cli/index.ts +3 -0
- package/src/cli/services/AlephaCliUtils.ts +10 -154
- package/src/cli/services/GitMessageParser.ts +77 -0
- package/src/command/helpers/EnvUtils.ts +37 -0
- package/src/command/index.ts +3 -1
- package/src/command/primitives/$command.ts +172 -6
- package/src/command/providers/CliProvider.ts +499 -95
- package/src/core/Alepha.ts +1 -1
- package/src/core/providers/SchemaValidator.ts +23 -1
- package/src/file/providers/NodeFileSystemProvider.ts +3 -1
- package/src/mcp/errors/McpError.ts +72 -0
- package/src/mcp/helpers/jsonrpc.ts +163 -0
- package/src/mcp/index.ts +132 -0
- package/src/mcp/interfaces/McpTypes.ts +248 -0
- package/src/mcp/primitives/$prompt.ts +188 -0
- package/src/mcp/primitives/$resource.ts +171 -0
- package/src/mcp/primitives/$tool.ts +285 -0
- package/src/mcp/providers/McpServerProvider.ts +382 -0
- package/src/mcp/transports/SseMcpTransport.ts +172 -0
- package/src/mcp/transports/StdioMcpTransport.ts +126 -0
- package/src/orm/index.ts +20 -4
- package/src/orm/interfaces/PgQueryWhere.ts +1 -26
- package/src/orm/providers/drivers/BunPostgresProvider.ts +225 -0
- package/src/orm/providers/drivers/BunSqliteProvider.ts +180 -0
- package/src/orm/providers/drivers/CloudflareD1Provider.ts +164 -0
- package/src/orm/providers/drivers/DatabaseProvider.ts +25 -0
- package/src/orm/providers/drivers/NodePostgresProvider.ts +0 -25
- package/src/orm/providers/drivers/NodeSqliteProvider.ts +3 -1
- package/src/orm/services/QueryManager.ts +10 -125
- package/src/queue/redis/providers/RedisQueueProvider.ts +2 -7
- package/src/redis/index.ts +65 -3
- package/src/redis/providers/BunRedisProvider.ts +304 -0
- package/src/redis/providers/BunRedisSubscriberProvider.ts +94 -0
- package/src/redis/providers/NodeRedisProvider.ts +280 -0
- package/src/redis/providers/NodeRedisSubscriberProvider.ts +94 -0
- package/src/redis/providers/RedisProvider.ts +134 -140
- package/src/redis/providers/RedisSubscriberProvider.ts +58 -49
- package/src/server/core/providers/BunHttpServerProvider.ts +0 -3
- package/src/server/core/providers/ServerBodyParserProvider.ts +3 -1
- package/src/server/core/providers/ServerProvider.ts +7 -4
- package/src/server/multipart/providers/ServerMultipartProvider.ts +3 -1
- package/src/server/proxy/providers/ServerProxyProvider.ts +1 -1
- package/src/topic/redis/providers/RedisTopicProvider.ts +3 -3
- package/src/vite/plugins/viteAlephaBuild.ts +8 -2
- package/src/vite/plugins/viteAlephaDev.ts +6 -2
- package/src/vite/tasks/buildServer.ts +2 -1
- package/src/vite/tasks/generateCloudflare.ts +43 -15
- package/src/vite/tasks/runAlepha.ts +1 -0
- 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
|
+
}
|