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.
- package/README.md +3 -3
- package/dist/api/audits/index.d.ts +80 -1
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts +80 -1
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +236 -157
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/notifications/index.d.ts +21 -1
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/parameters/index.d.ts +451 -4
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/users/index.d.ts +252 -249
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +4 -0
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +128 -128
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/batch/index.js.map +1 -1
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cli/index.d.ts +304 -115
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +650 -531
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +210 -13
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +306 -69
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +7 -6
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +7 -6
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.js.map +1 -1
- package/dist/fake/index.js.map +1 -1
- package/dist/file/index.d.ts.map +1 -1
- package/dist/file/index.js.map +1 -1
- package/dist/lock/redis/index.js.map +1 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/index.browser.js +26 -5
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.d.ts +294 -215
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +522 -523
- package/dist/orm/index.js.map +1 -1
- 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 -1
- package/dist/redis/index.js +412 -21
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.js.map +1 -1
- package/dist/router/index.js.map +1 -1
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +155 -155
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/cookies/index.browser.js.map +1 -1
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts +0 -1
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/helmet/index.d.ts +4 -1
- package/dist/server/helmet/index.d.ts.map +1 -1
- package/dist/server/helmet/index.js.map +1 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/multipart/index.d.ts.map +1 -1
- package/dist/server/multipart/index.js.map +1 -1
- package/dist/server/proxy/index.js.map +1 -1
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/security/index.d.ts +9 -9
- package/dist/server/security/index.js.map +1 -1
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/core/index.js.map +1 -1
- package/dist/topic/redis/index.js +3 -3
- package/dist/topic/redis/index.js.map +1 -1
- package/dist/vite/index.js +9 -6
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +7 -7
- package/dist/websocket/index.js.map +1 -1
- package/package.json +3 -3
- package/src/api/users/index.ts +4 -0
- package/src/cli/apps/AlephaCli.ts +36 -14
- package/src/cli/apps/AlephaPackageBuilderCli.ts +5 -1
- package/src/cli/assets/appRouterTs.ts +1 -1
- package/src/cli/atoms/changelogOptions.ts +45 -0
- package/src/cli/commands/{ViteCommands.ts → build.ts} +4 -93
- package/src/cli/commands/changelog.ts +244 -0
- package/src/cli/commands/clean.ts +14 -0
- package/src/cli/commands/{DrizzleCommands.ts → db.ts} +37 -124
- package/src/cli/commands/deploy.ts +118 -0
- package/src/cli/commands/dev.ts +57 -0
- package/src/cli/commands/format.ts +17 -0
- package/src/cli/commands/{CoreCommands.ts → init.ts} +2 -40
- package/src/cli/commands/lint.ts +17 -0
- package/src/cli/commands/root.ts +32 -0
- package/src/cli/commands/run.ts +24 -0
- package/src/cli/commands/test.ts +42 -0
- package/src/cli/commands/typecheck.ts +19 -0
- package/src/cli/commands/{VerifyCommands.ts → verify.ts} +1 -13
- package/src/cli/defineConfig.ts +24 -0
- package/src/cli/index.ts +17 -5
- package/src/cli/services/AlephaCliUtils.ts +4 -21
- 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 +424 -91
- package/src/core/Alepha.ts +8 -5
- package/src/file/providers/NodeFileSystemProvider.ts +3 -1
- package/src/orm/index.browser.ts +1 -1
- package/src/orm/index.ts +18 -10
- package/src/orm/interfaces/PgQueryWhere.ts +1 -26
- package/src/orm/providers/{PostgresTypeProvider.ts → DatabaseTypeProvider.ts} +25 -3
- package/src/orm/providers/drivers/BunPostgresProvider.ts +225 -0
- package/src/orm/providers/drivers/BunSqliteProvider.ts +180 -0
- package/src/orm/providers/drivers/DatabaseProvider.ts +25 -0
- package/src/orm/providers/drivers/NodePostgresProvider.ts +0 -25
- 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/tasks/buildServer.ts +1 -0
- package/src/cli/commands/BiomeCommands.ts +0 -29
- package/src/cli/commands/ChangelogCommands.ts +0 -389
- 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:
|
|
144
|
+
* id: db.primaryKey(),
|
|
139
145
|
* name: t.text(),
|
|
140
146
|
* email: t.text(),
|
|
141
147
|
* }),
|
|
142
148
|
* });
|
|
143
149
|
*
|
|
144
|
-
* class
|
|
150
|
+
* class App {
|
|
145
151
|
* users = $repository(users);
|
|
146
|
-
* }
|
|
147
152
|
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
}
|