@rx-ted/packages-honest-plugins 1.0.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/CHANGELOG.md +13 -0
- package/README.md +45 -0
- package/api-doc/.turbo/turbo-build.log +1 -0
- package/api-doc/.turbo/turbo-test.log +11 -0
- package/api-doc/.turbo/turbo-typecheck.log +1 -0
- package/api-doc/CHANGELOG.md +22 -0
- package/api-doc/README.md +80 -0
- package/api-doc/dist/api-doc.plugin.d.ts +53 -0
- package/api-doc/dist/api-doc.plugin.js +452 -0
- package/api-doc/dist/index.d.ts +5 -0
- package/api-doc/dist/index.js +3 -0
- package/api-doc/dist/interfaces/renderer.interface.d.ts +7 -0
- package/api-doc/dist/interfaces/renderer.interface.js +1 -0
- package/api-doc/dist/renderers/scalar.renderer.d.ts +10 -0
- package/api-doc/dist/renderers/scalar.renderer.js +76 -0
- package/api-doc/dist/renderers/swagger.renderer.d.ts +10 -0
- package/api-doc/dist/renderers/swagger.renderer.js +83 -0
- package/api-doc/node_modules/.bin/esbuild +22 -0
- package/api-doc/node_modules/.bin/jiti +22 -0
- package/api-doc/node_modules/.bin/sass +22 -0
- package/api-doc/node_modules/.bin/tsx +22 -0
- package/api-doc/node_modules/.bin/vite +22 -0
- package/api-doc/node_modules/.bin/vitest +22 -0
- package/api-doc/node_modules/.bin/yaml +22 -0
- package/api-doc/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/api-doc/package.json +48 -0
- package/api-doc/src/api-doc.plugin.ts +554 -0
- package/api-doc/src/index.ts +5 -0
- package/api-doc/src/interfaces/renderer.interface.ts +5 -0
- package/api-doc/src/plugin.test.ts +740 -0
- package/api-doc/src/renderers/scalar.renderer.ts +82 -0
- package/api-doc/src/renderers/swagger.renderer.ts +89 -0
- package/api-doc/tsconfig.json +21 -0
- package/api-doc/vitest.config.ts +13 -0
- package/cache/.turbo/turbo-build.log +1 -0
- package/cache/.turbo/turbo-test.log +16 -0
- package/cache/CHANGELOG.md +23 -0
- package/cache/README.md +79 -0
- package/cache/dist/cache-service.d.ts +8 -0
- package/cache/dist/cache-service.js +21 -0
- package/cache/dist/cacheable.d.ts +2 -0
- package/cache/dist/cacheable.js +10 -0
- package/cache/dist/cloudflare/driver.d.ts +23 -0
- package/cache/dist/cloudflare/driver.js +102 -0
- package/cache/dist/cloudflare/index.d.ts +3 -0
- package/cache/dist/cloudflare/index.js +2 -0
- package/cache/dist/cloudflare/plugin.d.ts +19 -0
- package/cache/dist/cloudflare/plugin.js +58 -0
- package/cache/dist/index.d.ts +11 -0
- package/cache/dist/index.js +14 -0
- package/cache/dist/local/driver.d.ts +2 -0
- package/cache/dist/local/driver.js +98 -0
- package/cache/dist/local/index.d.ts +2 -0
- package/cache/dist/local/index.js +2 -0
- package/cache/dist/local/plugin.d.ts +14 -0
- package/cache/dist/local/plugin.js +24 -0
- package/cache/dist/redis/driver.d.ts +10 -0
- package/cache/dist/redis/driver.js +102 -0
- package/cache/dist/redis/index.d.ts +4 -0
- package/cache/dist/redis/index.js +2 -0
- package/cache/dist/redis/plugin.d.ts +25 -0
- package/cache/dist/redis/plugin.js +61 -0
- package/cache/dist/resolve.d.ts +24 -0
- package/cache/dist/resolve.js +114 -0
- package/cache/dist/types.d.ts +18 -0
- package/cache/dist/types.js +1 -0
- package/cache/node_modules/.bin/esbuild +22 -0
- package/cache/node_modules/.bin/jiti +22 -0
- package/cache/node_modules/.bin/sass +22 -0
- package/cache/node_modules/.bin/tsx +22 -0
- package/cache/node_modules/.bin/vite +22 -0
- package/cache/node_modules/.bin/vitest +22 -0
- package/cache/node_modules/.bin/yaml +22 -0
- package/cache/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/cache/package.json +46 -0
- package/cache/src/cache-service.test.ts +16 -0
- package/cache/src/cache-service.ts +15 -0
- package/cache/src/cacheable.test.ts +78 -0
- package/cache/src/cacheable.ts +16 -0
- package/cache/src/cloudflare/driver.test.ts +188 -0
- package/cache/src/cloudflare/driver.ts +118 -0
- package/cache/src/cloudflare/index.ts +3 -0
- package/cache/src/cloudflare/plugin.ts +71 -0
- package/cache/src/index.test.ts +68 -0
- package/cache/src/index.ts +20 -0
- package/cache/src/local/driver.ts +117 -0
- package/cache/src/local/index.ts +2 -0
- package/cache/src/local/plugin.ts +34 -0
- package/cache/src/redis/driver.test.ts +71 -0
- package/cache/src/redis/driver.ts +124 -0
- package/cache/src/redis/index.ts +4 -0
- package/cache/src/redis/plugin.ts +84 -0
- package/cache/src/resolve.test.ts +257 -0
- package/cache/src/resolve.ts +140 -0
- package/cache/src/types.ts +14 -0
- package/cache/tsconfig.json +21 -0
- package/cache/vitest.config.ts +13 -0
- package/db/.turbo/turbo-build.log +1 -0
- package/db/.turbo/turbo-test.log +21 -0
- package/db/CHANGELOG.md +26 -0
- package/db/README.md +99 -0
- package/db/dist/constants.d.ts +1 -0
- package/db/dist/constants.js +1 -0
- package/db/dist/d1/index.d.ts +3 -0
- package/db/dist/d1/index.js +2 -0
- package/db/dist/d1/plugin.d.ts +18 -0
- package/db/dist/d1/plugin.js +104 -0
- package/db/dist/db-service.d.ts +7 -0
- package/db/dist/db-service.js +21 -0
- package/db/dist/index.d.ts +15 -0
- package/db/dist/index.js +20 -0
- package/db/dist/mysql/client.d.ts +36 -0
- package/db/dist/mysql/client.js +84 -0
- package/db/dist/mysql/driver.d.ts +18 -0
- package/db/dist/mysql/driver.js +107 -0
- package/db/dist/mysql/index.d.ts +11 -0
- package/db/dist/mysql/index.js +11 -0
- package/db/dist/mysql/plugin.d.ts +51 -0
- package/db/dist/mysql/plugin.js +94 -0
- package/db/dist/mysql/types.d.ts +38 -0
- package/db/dist/mysql/types.js +1 -0
- package/db/dist/postgres/index.d.ts +2 -0
- package/db/dist/postgres/index.js +3 -0
- package/db/dist/repository.d.ts +14 -0
- package/db/dist/repository.js +79 -0
- package/db/dist/resolve.d.ts +6 -0
- package/db/dist/resolve.js +81 -0
- package/db/dist/schema-builder/column-builder.d.ts +26 -0
- package/db/dist/schema-builder/column-builder.js +53 -0
- package/db/dist/schema-builder/compile-d1.d.ts +5 -0
- package/db/dist/schema-builder/compile-d1.js +67 -0
- package/db/dist/schema-builder/compile-mysql.d.ts +12 -0
- package/db/dist/schema-builder/compile-mysql.js +78 -0
- package/db/dist/schema-builder/index.d.ts +8 -0
- package/db/dist/schema-builder/index.js +22 -0
- package/db/dist/schema-builder/table-builder.d.ts +16 -0
- package/db/dist/schema-builder/table-builder.js +38 -0
- package/db/dist/schema-builder/types.d.ts +26 -0
- package/db/dist/schema-builder/types.js +1 -0
- package/db/dist/schema-builder/zod-bridge.d.ts +24 -0
- package/db/dist/schema-builder/zod-bridge.js +53 -0
- package/db/dist/sqlite/index.d.ts +4 -0
- package/db/dist/sqlite/index.js +2 -0
- package/db/dist/sqlite/plugin.d.ts +17 -0
- package/db/dist/sqlite/plugin.js +33 -0
- package/db/dist/types.d.ts +42 -0
- package/db/dist/types.js +1 -0
- package/db/node_modules/.bin/esbuild +22 -0
- package/db/node_modules/.bin/jiti +22 -0
- package/db/node_modules/.bin/sass +22 -0
- package/db/node_modules/.bin/tsx +22 -0
- package/db/node_modules/.bin/vite +22 -0
- package/db/node_modules/.bin/vitest +22 -0
- package/db/node_modules/.bin/yaml +22 -0
- package/db/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/db/package.json +59 -0
- package/db/src/constants.test.ts +8 -0
- package/db/src/constants.ts +1 -0
- package/db/src/d1/index.ts +13 -0
- package/db/src/d1/plugin.ts +135 -0
- package/db/src/db-service.ts +14 -0
- package/db/src/index.test.ts +63 -0
- package/db/src/index.ts +57 -0
- package/db/src/mysql/client.ts +112 -0
- package/db/src/mysql/driver.test.ts +190 -0
- package/db/src/mysql/driver.ts +116 -0
- package/db/src/mysql/index.ts +51 -0
- package/db/src/mysql/plugin.test.ts +228 -0
- package/db/src/mysql/plugin.ts +128 -0
- package/db/src/mysql/types.ts +32 -0
- package/db/src/postgres/index.ts +15 -0
- package/db/src/repository.test.ts +195 -0
- package/db/src/repository.ts +95 -0
- package/db/src/resolve.ts +105 -0
- package/db/src/schema-builder/column-builder.test.ts +55 -0
- package/db/src/schema-builder/column-builder.ts +87 -0
- package/db/src/schema-builder/compile-d1.test.ts +60 -0
- package/db/src/schema-builder/compile-d1.ts +70 -0
- package/db/src/schema-builder/compile-mysql.test.ts +64 -0
- package/db/src/schema-builder/compile-mysql.ts +102 -0
- package/db/src/schema-builder/index.test.ts +36 -0
- package/db/src/schema-builder/index.ts +41 -0
- package/db/src/schema-builder/table-builder.test.ts +55 -0
- package/db/src/schema-builder/table-builder.ts +50 -0
- package/db/src/schema-builder/types.test.ts +68 -0
- package/db/src/schema-builder/types.ts +30 -0
- package/db/src/schema-builder/zod-bridge.ts +89 -0
- package/db/src/sqlite/index.ts +14 -0
- package/db/src/sqlite/plugin.ts +46 -0
- package/db/src/types.ts +45 -0
- package/db/tsconfig.json +21 -0
- package/db/vitest.config.ts +13 -0
- package/mail/.turbo/turbo-build.log +1 -0
- package/mail/.turbo/turbo-test.log +12 -0
- package/mail/.turbo/turbo-typecheck.log +1 -0
- package/mail/CHANGELOG.md +25 -0
- package/mail/README.md +214 -0
- package/mail/dist/index.d.ts +4 -0
- package/mail/dist/index.js +2 -0
- package/mail/dist/plugin.d.ts +64 -0
- package/mail/dist/plugin.js +232 -0
- package/mail/dist/providers/brevo.provider.d.ts +15 -0
- package/mail/dist/providers/brevo.provider.js +75 -0
- package/mail/dist/providers/custom.provider.d.ts +9 -0
- package/mail/dist/providers/custom.provider.js +22 -0
- package/mail/dist/providers/resend.provider.d.ts +16 -0
- package/mail/dist/providers/resend.provider.js +77 -0
- package/mail/dist/providers/smtp.provider.d.ts +21 -0
- package/mail/dist/providers/smtp.provider.js +86 -0
- package/mail/dist/rate-limiter.d.ts +16 -0
- package/mail/dist/rate-limiter.js +49 -0
- package/mail/dist/types.d.ts +68 -0
- package/mail/dist/types.js +6 -0
- package/mail/node_modules/.bin/esbuild +22 -0
- package/mail/node_modules/.bin/jiti +22 -0
- package/mail/node_modules/.bin/sass +22 -0
- package/mail/node_modules/.bin/tsx +22 -0
- package/mail/node_modules/.bin/vite +22 -0
- package/mail/node_modules/.bin/vitest +22 -0
- package/mail/node_modules/.bin/yaml +22 -0
- package/mail/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/mail/package.json +52 -0
- package/mail/src/index.ts +4 -0
- package/mail/src/plugin.test.ts +219 -0
- package/mail/src/plugin.ts +313 -0
- package/mail/src/providers/brevo.provider.ts +78 -0
- package/mail/src/providers/custom.provider.ts +29 -0
- package/mail/src/providers/resend.provider.ts +74 -0
- package/mail/src/providers/smtp.provider.ts +85 -0
- package/mail/src/rate-limiter.test.ts +81 -0
- package/mail/src/rate-limiter.ts +54 -0
- package/mail/src/types.ts +81 -0
- package/mail/tsconfig.json +21 -0
- package/mail/vitest.config.ts +13 -0
- package/package.json +36 -0
- package/s3/.turbo/turbo-build.log +1 -0
- package/s3/.turbo/turbo-test.log +11 -0
- package/s3/CHANGELOG.md +20 -0
- package/s3/README.md +90 -0
- package/s3/dist/index.d.ts +6 -0
- package/s3/dist/index.js +4 -0
- package/s3/dist/s3-service.d.ts +7 -0
- package/s3/dist/s3-service.js +21 -0
- package/s3/dist/s3.driver.d.ts +6 -0
- package/s3/dist/s3.driver.js +221 -0
- package/s3/dist/s3.plugin.d.ts +24 -0
- package/s3/dist/s3.plugin.js +58 -0
- package/s3/dist/types.d.ts +30 -0
- package/s3/dist/types.js +1 -0
- package/s3/node_modules/.bin/esbuild +22 -0
- package/s3/node_modules/.bin/jiti +22 -0
- package/s3/node_modules/.bin/sass +22 -0
- package/s3/node_modules/.bin/tsx +22 -0
- package/s3/node_modules/.bin/vite +22 -0
- package/s3/node_modules/.bin/vitest +22 -0
- package/s3/node_modules/.bin/yaml +22 -0
- package/s3/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/s3/package.json +47 -0
- package/s3/src/index.test.ts +20 -0
- package/s3/src/index.ts +13 -0
- package/s3/src/s3-service.ts +14 -0
- package/s3/src/s3.driver.ts +259 -0
- package/s3/src/s3.plugin.ts +80 -0
- package/s3/src/types.ts +37 -0
- package/s3/tsconfig.json +21 -0
- package/s3/vitest.config.ts +13 -0
package/db/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rx-ted/packages-honest-plugins-db",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Database plugin for @rx-ted/packages-honest",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"vitest": "./src/index.ts",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./d1": {
|
|
16
|
+
"vitest": "./src/d1/index.ts",
|
|
17
|
+
"types": "./dist/d1/index.d.ts",
|
|
18
|
+
"default": "./dist/d1/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./sqlite": {
|
|
21
|
+
"vitest": "./src/sqlite/index.ts",
|
|
22
|
+
"types": "./dist/sqlite/index.d.ts",
|
|
23
|
+
"default": "./dist/sqlite/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"sideEffects": [
|
|
27
|
+
"./dist/index.js"
|
|
28
|
+
],
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/rx-ted/honest.git"
|
|
32
|
+
},
|
|
33
|
+
"author": "rx-ted",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"files": [
|
|
36
|
+
"dist"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -p tsconfig.json",
|
|
40
|
+
"test": "vitest run",
|
|
41
|
+
"test:watch": "vitest",
|
|
42
|
+
"test:coverage": "vitest run --coverage"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@libsql/client": "^0.17.4",
|
|
46
|
+
"@rx-ted/packages-core": "workspace:^",
|
|
47
|
+
"drizzle-orm": "^0.45.2",
|
|
48
|
+
"mysql2": "^3.22.4",
|
|
49
|
+
"zod": "^4.4.3"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@rx-ted/packages-honest": "workspace:^",
|
|
53
|
+
"hono": "^4.12.18"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^22.15.3",
|
|
57
|
+
"vitest": "^4.1.7"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DB_GLOBAL_KEY = 'app:db';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export {
|
|
2
|
+
sqliteTable,
|
|
3
|
+
text,
|
|
4
|
+
integer,
|
|
5
|
+
real,
|
|
6
|
+
blob,
|
|
7
|
+
uniqueIndex,
|
|
8
|
+
index,
|
|
9
|
+
primaryKey,
|
|
10
|
+
foreignKey,
|
|
11
|
+
} from 'drizzle-orm/sqlite-core';
|
|
12
|
+
export type { AnyD1Database as D1DrizzleDatabase } from 'drizzle-orm/d1';
|
|
13
|
+
export { D1Plugin } from './plugin';
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { Hono } from 'hono';
|
|
2
|
+
import {
|
|
3
|
+
ENV_SYMBOL,
|
|
4
|
+
Platform,
|
|
5
|
+
type Env,
|
|
6
|
+
type ILogger,
|
|
7
|
+
resolveBinding,
|
|
8
|
+
} from '@rx-ted/packages-core';
|
|
9
|
+
import type { IPlugin, Application } from '@rx-ted/packages-honest';
|
|
10
|
+
import { ComponentManager, resolvePluginLogger } from '@rx-ted/packages-honest';
|
|
11
|
+
import { drizzle } from 'drizzle-orm/d1';
|
|
12
|
+
import { DB_GLOBAL_KEY } from '../constants';
|
|
13
|
+
import { assertRuntimeSupport, type AppRuntime } from '../resolve';
|
|
14
|
+
|
|
15
|
+
export class D1Plugin implements IPlugin {
|
|
16
|
+
readonly name = `${DB_GLOBAL_KEY}:d1`;
|
|
17
|
+
readonly version = '0.0.1';
|
|
18
|
+
logger?: ILogger;
|
|
19
|
+
private db: any = null;
|
|
20
|
+
|
|
21
|
+
constructor(private options: { binding?: string; schema: Record<string, any> }) {}
|
|
22
|
+
|
|
23
|
+
async beforeModulesRegistered(app: Application, _hono: Hono): Promise<void> {
|
|
24
|
+
this.logger ??= resolvePluginLogger(this.name);
|
|
25
|
+
|
|
26
|
+
const appEnv = ComponentManager.getPlugin<Env>(ENV_SYMBOL);
|
|
27
|
+
assertRuntimeSupport(appEnv.platform as AppRuntime, 'd1');
|
|
28
|
+
|
|
29
|
+
const bindingName = this.resolveBindingName();
|
|
30
|
+
this.logger.info(`connecting... (binding: ${bindingName})`);
|
|
31
|
+
|
|
32
|
+
const binding = resolveBinding(bindingName);
|
|
33
|
+
|
|
34
|
+
if (binding) {
|
|
35
|
+
this.db = drizzle(binding, { schema: this.options.schema });
|
|
36
|
+
ComponentManager.registerPlugin(DB_GLOBAL_KEY, this.db);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const resolveName = this.resolveBindingName.bind(this);
|
|
41
|
+
const opts = this.options;
|
|
42
|
+
const ensure = () => {
|
|
43
|
+
if (!this.db) {
|
|
44
|
+
const bn = resolveName();
|
|
45
|
+
const b = resolveBinding(bn);
|
|
46
|
+
if (b) {
|
|
47
|
+
this.db = drizzle(b, { schema: opts.schema });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return this.db;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
ComponentManager.registerPlugin(
|
|
54
|
+
DB_GLOBAL_KEY,
|
|
55
|
+
new Proxy(
|
|
56
|
+
{},
|
|
57
|
+
{
|
|
58
|
+
get(_, prop) {
|
|
59
|
+
const instance = ensure();
|
|
60
|
+
if (!instance) {
|
|
61
|
+
throw new Error(`D1 binding "${resolveName()}" not found`);
|
|
62
|
+
}
|
|
63
|
+
const val = (instance as any)[prop];
|
|
64
|
+
return typeof val === 'function' ? val.bind(instance) : val;
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async afterModulesRegistered(_app: Application, _hono: Hono): Promise<void> {
|
|
72
|
+
if (!this.logger) return;
|
|
73
|
+
|
|
74
|
+
const bindingName = this.resolveBindingName();
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
if (!this.db) {
|
|
78
|
+
const binding = resolveBinding(bindingName);
|
|
79
|
+
if (!binding) {
|
|
80
|
+
this.logger.warn(`health check skipped — binding "${bindingName}" not resolved`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
this.db = drizzle(binding, { schema: this.options.schema });
|
|
84
|
+
ComponentManager.registerPlugin(DB_GLOBAL_KEY, this.db);
|
|
85
|
+
this.logger.info(`connected (binding: ${bindingName})`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await healthCheck(this.db, bindingName);
|
|
89
|
+
this.logger.info(`health check passed (binding: ${bindingName})`);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
this.logger.error(
|
|
92
|
+
{ error: error instanceof Error ? error.message : String(error) },
|
|
93
|
+
`health check failed (binding: ${bindingName})`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async close(): Promise<void> {
|
|
99
|
+
this.logger?.info(`shutting down (binding: ${this.resolveBindingName()})`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private resolveBindingName(): string {
|
|
103
|
+
if (this.options.binding) return this.options.binding;
|
|
104
|
+
return findD1Binding() ?? 'DB';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isD1Database(value: unknown): value is object {
|
|
109
|
+
return (
|
|
110
|
+
value !== null && typeof value === 'object' && typeof (value as any).prepare === 'function'
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function findD1Binding(): string | null {
|
|
115
|
+
const sources: unknown[] = [globalThis, (globalThis as any).env, Platform.env()];
|
|
116
|
+
for (const source of sources) {
|
|
117
|
+
if (!source || typeof source !== 'object') continue;
|
|
118
|
+
for (const key of Object.keys(source as Record<string, unknown>)) {
|
|
119
|
+
if (isD1Database((source as any)[key])) return key;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function healthCheck(db: any, bindingName: string): Promise<void> {
|
|
126
|
+
try {
|
|
127
|
+
await db.run('SELECT 1');
|
|
128
|
+
} catch (cause) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`D1 health check failed for binding "${bindingName}": the binding has no active database session. ` +
|
|
131
|
+
`Ensure the D1 database is bound correctly in your wrangler.toml and has been created (npx wrangler d1 create <name>).`,
|
|
132
|
+
{ cause },
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Service, ComponentManager } from '@rx-ted/packages-honest';
|
|
2
|
+
import type { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core';
|
|
3
|
+
import { DB_GLOBAL_KEY } from './constants';
|
|
4
|
+
|
|
5
|
+
@Service()
|
|
6
|
+
class DbService {
|
|
7
|
+
constructor() {
|
|
8
|
+
return ComponentManager.getPlugin(DB_GLOBAL_KEY);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface DbService extends BaseSQLiteDatabase<'async', any> {}
|
|
13
|
+
|
|
14
|
+
export { DbService };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import * as plugin from './index';
|
|
3
|
+
|
|
4
|
+
describe('plugin barrel exports', () => {
|
|
5
|
+
it('exports DB_GLOBAL_KEY', () => {
|
|
6
|
+
expect(plugin.DB_GLOBAL_KEY).toBe('app:db');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('exports DbService (class)', () => {
|
|
10
|
+
expect(plugin.DbService).toBeDefined();
|
|
11
|
+
expect(typeof plugin.DbService).toBe('function');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('exports BaseRepository (class)', () => {
|
|
15
|
+
expect(plugin.BaseRepository).toBeDefined();
|
|
16
|
+
expect(typeof plugin.BaseRepository).toBe('function');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('exports core Drizzle operators', () => {
|
|
20
|
+
expect(plugin.eq).toBeDefined();
|
|
21
|
+
expect(plugin.and).toBeDefined();
|
|
22
|
+
expect(plugin.or).toBeDefined();
|
|
23
|
+
expect(plugin.not).toBeDefined();
|
|
24
|
+
expect(plugin.sql).toBeDefined();
|
|
25
|
+
expect(plugin.asc).toBeDefined();
|
|
26
|
+
expect(plugin.desc).toBeDefined();
|
|
27
|
+
expect(plugin.count).toBeDefined();
|
|
28
|
+
expect(typeof plugin.eq).toBe('function');
|
|
29
|
+
expect(typeof plugin.and).toBe('function');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('exports MysqlPlugin', () => {
|
|
33
|
+
expect(plugin.MysqlPlugin).toBeDefined();
|
|
34
|
+
expect(typeof plugin.MysqlPlugin).toBe('function');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('exports mysql namespace', () => {
|
|
38
|
+
expect(plugin.mysql).toBeDefined();
|
|
39
|
+
expect(plugin.mysql.MysqlPlugin).toBeDefined();
|
|
40
|
+
expect(plugin.mysql.mysqlTable).toBeDefined();
|
|
41
|
+
expect(plugin.mysql.varchar).toBeDefined();
|
|
42
|
+
expect(plugin.mysql.int).toBeDefined();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('exports MySQL column builders at top level', () => {
|
|
46
|
+
expect(plugin.mysqlTable).toBeDefined();
|
|
47
|
+
expect(plugin.varchar).toBeDefined();
|
|
48
|
+
expect(plugin.int).toBeDefined();
|
|
49
|
+
expect(plugin.bigint).toBeDefined();
|
|
50
|
+
expect(plugin.datetime).toBeDefined();
|
|
51
|
+
expect(plugin.boolean).toBeDefined();
|
|
52
|
+
expect(plugin.json).toBeDefined();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('exports type-only namespaces (no runtime value)', () => {
|
|
56
|
+
// These are type-only namespaces (re-exports of .d.ts modules)
|
|
57
|
+
// They may not have runtime values depending on the Drizzle version
|
|
58
|
+
// Just verify the exports exist without throwing
|
|
59
|
+
expect(plugin).toHaveProperty('postgres');
|
|
60
|
+
expect(plugin).toHaveProperty('sqlite');
|
|
61
|
+
expect(plugin).toHaveProperty('d1');
|
|
62
|
+
});
|
|
63
|
+
});
|
package/db/src/index.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Common exports
|
|
2
|
+
export { DB_GLOBAL_KEY } from './constants';
|
|
3
|
+
export { DBPlugin, assertRuntimeSupport } from './resolve';
|
|
4
|
+
export type { AppRuntime, DbType } from './resolve';
|
|
5
|
+
export { DbService } from './db-service';
|
|
6
|
+
export { BaseRepository } from './repository';
|
|
7
|
+
export type {
|
|
8
|
+
DatabaseClient,
|
|
9
|
+
PreparedStatement,
|
|
10
|
+
DatabaseConnection,
|
|
11
|
+
QueryResult,
|
|
12
|
+
DatabaseDriver,
|
|
13
|
+
QueryOptions,
|
|
14
|
+
PaginationResult,
|
|
15
|
+
} from './types';
|
|
16
|
+
|
|
17
|
+
// Common Drizzle operators
|
|
18
|
+
export {
|
|
19
|
+
eq,
|
|
20
|
+
and,
|
|
21
|
+
or,
|
|
22
|
+
not,
|
|
23
|
+
inArray,
|
|
24
|
+
sql,
|
|
25
|
+
asc,
|
|
26
|
+
desc,
|
|
27
|
+
count,
|
|
28
|
+
like,
|
|
29
|
+
isNull,
|
|
30
|
+
isNotNull,
|
|
31
|
+
} from 'drizzle-orm';
|
|
32
|
+
|
|
33
|
+
// MySQL dialect (top-level for backward compatibility + namespaced)
|
|
34
|
+
export * from './mysql';
|
|
35
|
+
export * as mysql from './mysql';
|
|
36
|
+
|
|
37
|
+
// PostgreSQL dialect (namespaced only)
|
|
38
|
+
export * as postgres from './postgres';
|
|
39
|
+
|
|
40
|
+
// SQLite dialect (namespaced only)
|
|
41
|
+
export * as sqlite from './sqlite';
|
|
42
|
+
|
|
43
|
+
// Cloudflare D1 dialect (namespaced only)
|
|
44
|
+
export * as d1 from './d1';
|
|
45
|
+
|
|
46
|
+
// Portable schema builder (table builder + compile dispatcher)
|
|
47
|
+
export * from './schema-builder';
|
|
48
|
+
|
|
49
|
+
// Zod bridge
|
|
50
|
+
export {
|
|
51
|
+
zdb,
|
|
52
|
+
getZodDbMeta,
|
|
53
|
+
isZodObject,
|
|
54
|
+
toTableDefinition,
|
|
55
|
+
toColumnDefinition,
|
|
56
|
+
} from './schema-builder/zod-bridge';
|
|
57
|
+
export type { ZodDbMeta } from './schema-builder/zod-bridge';
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import MySQL, {
|
|
2
|
+
type Pool,
|
|
3
|
+
type PoolConnection,
|
|
4
|
+
type PoolOptions,
|
|
5
|
+
type ResultSetHeader,
|
|
6
|
+
type RowDataPacket,
|
|
7
|
+
} from 'mysql2/promise';
|
|
8
|
+
import type { DatabaseClient, DatabaseConnection, PreparedStatement, QueryResult } from '../types';
|
|
9
|
+
|
|
10
|
+
export function createMysqlPool(options: {
|
|
11
|
+
host: string;
|
|
12
|
+
port: number;
|
|
13
|
+
user: string;
|
|
14
|
+
password?: string;
|
|
15
|
+
database: string;
|
|
16
|
+
ssl?: boolean | { rejectUnauthorized?: boolean; ca?: string; cert?: string; key?: string };
|
|
17
|
+
connectionLimit?: number;
|
|
18
|
+
}): Pool {
|
|
19
|
+
const mysqlOptions: PoolOptions = {
|
|
20
|
+
host: options.host,
|
|
21
|
+
port: options.port,
|
|
22
|
+
user: options.user,
|
|
23
|
+
password: options.password,
|
|
24
|
+
database: options.database,
|
|
25
|
+
waitForConnections: true,
|
|
26
|
+
connectionLimit: options.connectionLimit ?? 10,
|
|
27
|
+
queueLimit: 0,
|
|
28
|
+
};
|
|
29
|
+
if (options.ssl) {
|
|
30
|
+
mysqlOptions.ssl = options.ssl === true ? { rejectUnauthorized: false } : options.ssl;
|
|
31
|
+
}
|
|
32
|
+
return MySQL.createPool(mysqlOptions);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function createMysqlClient(options: {
|
|
36
|
+
host: string;
|
|
37
|
+
port: number;
|
|
38
|
+
user: string;
|
|
39
|
+
password?: string;
|
|
40
|
+
database: string;
|
|
41
|
+
ssl?: boolean | { rejectUnauthorized?: boolean; ca?: string; cert?: string; key?: string };
|
|
42
|
+
}): Promise<DatabaseClient> {
|
|
43
|
+
const pool = createMysqlPool(options);
|
|
44
|
+
const connection = await pool.getConnection();
|
|
45
|
+
connection.release();
|
|
46
|
+
return new MysqlDatabaseClient(pool);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class MysqlDatabaseClient implements DatabaseClient {
|
|
50
|
+
constructor(private pool: Pool) {}
|
|
51
|
+
prepare(sql: string): PreparedStatement {
|
|
52
|
+
return new MysqlPreparedStatement(this.pool, sql);
|
|
53
|
+
}
|
|
54
|
+
async getConnection(): Promise<DatabaseConnection> {
|
|
55
|
+
const conn = await this.pool.getConnection();
|
|
56
|
+
return new MysqlDatabaseConnection(conn);
|
|
57
|
+
}
|
|
58
|
+
async close(): Promise<void> {
|
|
59
|
+
await this.pool.end();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
class MysqlPreparedStatement implements PreparedStatement {
|
|
64
|
+
constructor(
|
|
65
|
+
private client: Pool | PoolConnection,
|
|
66
|
+
private sql: string,
|
|
67
|
+
) {}
|
|
68
|
+
async execute(params?: unknown[]): Promise<QueryResult> {
|
|
69
|
+
const connection =
|
|
70
|
+
'getConnection' in this.client ? await this.client.getConnection() : this.client;
|
|
71
|
+
try {
|
|
72
|
+
const [result] = await connection.execute<ResultSetHeader>(
|
|
73
|
+
this.sql,
|
|
74
|
+
params as (string | number | Buffer)[],
|
|
75
|
+
);
|
|
76
|
+
return {
|
|
77
|
+
affectedRows: result.affectedRows,
|
|
78
|
+
insertId: result.insertId ? Number(result.insertId) : undefined,
|
|
79
|
+
};
|
|
80
|
+
} finally {
|
|
81
|
+
connection.release();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
release(): void {}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
class MysqlDatabaseConnection implements DatabaseConnection {
|
|
88
|
+
constructor(private connection: PoolConnection) {}
|
|
89
|
+
prepare(sql: string): PreparedStatement {
|
|
90
|
+
return new MysqlPreparedStatement(this.connection, sql);
|
|
91
|
+
}
|
|
92
|
+
async query<T>(sql: string, params?: unknown[]): Promise<T[]> {
|
|
93
|
+
const [rows] = await this.connection.query<RowDataPacket[]>(
|
|
94
|
+
sql,
|
|
95
|
+
params as (string | number | Buffer)[],
|
|
96
|
+
);
|
|
97
|
+
return rows as T[];
|
|
98
|
+
}
|
|
99
|
+
async execute(sql: string, params?: unknown[]): Promise<QueryResult> {
|
|
100
|
+
const [result] = await this.connection.execute<ResultSetHeader>(
|
|
101
|
+
sql,
|
|
102
|
+
params as (string | number | Buffer)[],
|
|
103
|
+
);
|
|
104
|
+
return {
|
|
105
|
+
affectedRows: result.affectedRows,
|
|
106
|
+
insertId: result.insertId ? Number(result.insertId) : undefined,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
release(): void {
|
|
110
|
+
this.connection.release();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
vi.mock('./client', () => ({
|
|
4
|
+
createMysqlClient: vi.fn(),
|
|
5
|
+
MysqlDatabaseClient: class {
|
|
6
|
+
constructor(public pool: any) {}
|
|
7
|
+
prepare = vi.fn();
|
|
8
|
+
getConnection = vi.fn();
|
|
9
|
+
close = vi.fn();
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
import { createMysqlDriver } from './driver';
|
|
14
|
+
import * as clientModule from './client';
|
|
15
|
+
import type { DatabaseClient, DatabaseConnection, PreparedStatement } from '../types';
|
|
16
|
+
|
|
17
|
+
function mockConnection(overrides?: Partial<DatabaseConnection>): DatabaseConnection {
|
|
18
|
+
return {
|
|
19
|
+
query: vi.fn(),
|
|
20
|
+
execute: vi.fn(),
|
|
21
|
+
prepare: vi.fn(),
|
|
22
|
+
release: vi.fn(),
|
|
23
|
+
...overrides,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function makeClient(conn?: DatabaseConnection) {
|
|
28
|
+
const c = conn ?? mockConnection();
|
|
29
|
+
return {
|
|
30
|
+
getConnection: vi.fn().mockResolvedValue(c),
|
|
31
|
+
prepare: vi.fn(),
|
|
32
|
+
close: vi.fn(),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function buildDriver(client?: DatabaseClient) {
|
|
37
|
+
const c = client ?? makeClient();
|
|
38
|
+
(clientModule.createMysqlClient as any).mockResolvedValue(c);
|
|
39
|
+
return createMysqlDriver({
|
|
40
|
+
host: 'localhost',
|
|
41
|
+
port: 3306,
|
|
42
|
+
user: 'root',
|
|
43
|
+
database: 'test',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe('MysqlDriver', () => {
|
|
48
|
+
describe('query', () => {
|
|
49
|
+
it('returns rows from connection', async () => {
|
|
50
|
+
const conn = mockConnection();
|
|
51
|
+
(conn.query as any).mockResolvedValue([{ id: 1, name: 'Alice' }]);
|
|
52
|
+
const driver = await buildDriver(makeClient(conn));
|
|
53
|
+
|
|
54
|
+
const result = await driver.query('SELECT * FROM users');
|
|
55
|
+
|
|
56
|
+
expect(result).toEqual([{ id: 1, name: 'Alice' }]);
|
|
57
|
+
expect(conn.query).toHaveBeenCalledWith('SELECT * FROM users', undefined);
|
|
58
|
+
expect(conn.release).toHaveBeenCalled();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('execute', () => {
|
|
63
|
+
it('prepares, executes and releases statement', async () => {
|
|
64
|
+
const stmt: PreparedStatement = {
|
|
65
|
+
execute: vi.fn().mockResolvedValue({ affectedRows: 2, insertId: 10 }),
|
|
66
|
+
release: vi.fn(),
|
|
67
|
+
};
|
|
68
|
+
const conn = mockConnection();
|
|
69
|
+
(conn.prepare as any).mockReturnValue(stmt);
|
|
70
|
+
const driver = await buildDriver(makeClient(conn));
|
|
71
|
+
|
|
72
|
+
const result = await driver.execute('UPDATE users SET name = ?', ['Bob']);
|
|
73
|
+
|
|
74
|
+
expect(result).toEqual({ affectedRows: 2, insertId: 10 });
|
|
75
|
+
expect(conn.prepare).toHaveBeenCalledWith('UPDATE users SET name = ?');
|
|
76
|
+
expect(stmt.execute).toHaveBeenCalledWith(['Bob']);
|
|
77
|
+
expect(stmt.release).toHaveBeenCalled();
|
|
78
|
+
expect(conn.release).toHaveBeenCalled();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('transaction', () => {
|
|
83
|
+
it('commits on success', async () => {
|
|
84
|
+
const conn = mockConnection();
|
|
85
|
+
const driver = await buildDriver(makeClient(conn));
|
|
86
|
+
|
|
87
|
+
const result = await driver.transaction(async (tx) => {
|
|
88
|
+
await tx.query('SELECT 1');
|
|
89
|
+
return 'ok';
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(result).toBe('ok');
|
|
93
|
+
expect(conn.query).toHaveBeenCalledWith('SET autocommit = 0');
|
|
94
|
+
expect(conn.query).toHaveBeenCalledWith('START TRANSACTION');
|
|
95
|
+
expect(conn.query).toHaveBeenCalledWith('COMMIT');
|
|
96
|
+
expect(conn.query).toHaveBeenCalledWith('SET autocommit = 1');
|
|
97
|
+
expect(conn.release).toHaveBeenCalled();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('rolls back on error and rethrows', async () => {
|
|
101
|
+
const conn = mockConnection();
|
|
102
|
+
const driver = await buildDriver(makeClient(conn));
|
|
103
|
+
|
|
104
|
+
await expect(
|
|
105
|
+
driver.transaction(async () => {
|
|
106
|
+
throw new Error('db error');
|
|
107
|
+
}),
|
|
108
|
+
).rejects.toThrow('db error');
|
|
109
|
+
|
|
110
|
+
expect(conn.query).toHaveBeenCalledWith('ROLLBACK');
|
|
111
|
+
expect(conn.query).toHaveBeenCalledWith('SET autocommit = 1');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('healthCheck', () => {
|
|
116
|
+
it('returns true when SELECT 1 succeeds', async () => {
|
|
117
|
+
const conn = mockConnection();
|
|
118
|
+
(conn.query as any).mockResolvedValue([[1]]);
|
|
119
|
+
const driver = await buildDriver(makeClient(conn));
|
|
120
|
+
|
|
121
|
+
const result = await driver.healthCheck();
|
|
122
|
+
|
|
123
|
+
expect(result).toBe(true);
|
|
124
|
+
expect(conn.query).toHaveBeenCalledWith('SELECT 1');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('returns false on error', async () => {
|
|
128
|
+
const conn = mockConnection();
|
|
129
|
+
(conn.query as any).mockRejectedValue(new Error('connection failed'));
|
|
130
|
+
const driver = await buildDriver(makeClient(conn));
|
|
131
|
+
|
|
132
|
+
const result = await driver.healthCheck();
|
|
133
|
+
|
|
134
|
+
expect(result).toBe(false);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('close', () => {
|
|
139
|
+
it('closes the underlying client', async () => {
|
|
140
|
+
const client = makeClient();
|
|
141
|
+
const driver = await buildDriver(client);
|
|
142
|
+
|
|
143
|
+
await driver.close();
|
|
144
|
+
|
|
145
|
+
expect(client.close).toHaveBeenCalled();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('TransactionalMysqlDriver', () => {
|
|
150
|
+
it('delegates query inside transaction', async () => {
|
|
151
|
+
const conn = mockConnection();
|
|
152
|
+
(conn.query as any).mockResolvedValue([{ id: 1 }]);
|
|
153
|
+
const driver = await buildDriver(makeClient(conn));
|
|
154
|
+
|
|
155
|
+
const result = await driver.transaction(async (tx) => {
|
|
156
|
+
return tx.query('SELECT 1');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(result).toEqual([{ id: 1 }]);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('throws on nested transaction call', async () => {
|
|
163
|
+
const conn = mockConnection();
|
|
164
|
+
const driver = await buildDriver(makeClient(conn));
|
|
165
|
+
|
|
166
|
+
await expect(
|
|
167
|
+
driver.transaction(async (tx) => {
|
|
168
|
+
return tx.transaction(async () => 'nested');
|
|
169
|
+
}),
|
|
170
|
+
).rejects.toThrow('Nested transactions are not supported');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('executes prepared statement inside transaction', async () => {
|
|
174
|
+
const stmt: PreparedStatement = {
|
|
175
|
+
execute: vi.fn().mockResolvedValue({ affectedRows: 1 }),
|
|
176
|
+
release: vi.fn(),
|
|
177
|
+
};
|
|
178
|
+
const conn = mockConnection();
|
|
179
|
+
(conn.prepare as any).mockReturnValue(stmt);
|
|
180
|
+
const driver = await buildDriver(makeClient(conn));
|
|
181
|
+
|
|
182
|
+
await driver.transaction(async (tx) => {
|
|
183
|
+
return tx.execute('UPDATE users SET x=1');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
expect(stmt.execute).toHaveBeenCalled();
|
|
187
|
+
expect(stmt.release).toHaveBeenCalled();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
});
|