@tobi2409/fullsty 0.5.1 → 0.5.3
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 +2 -2
- package/package.json +1 -1
- package/scripts/extension-wrappers/db-connection/db-connection-wrapper.ts +21 -9
- package/scripts/extension-wrappers/kysely/kysely-wrapper.ts +29 -0
- package/scripts/extension-wrappers/pg/pg-wrapper.ts +52 -61
- package/scripts/extension-wrappers/drizzle-orm/drizzle-orm-wrapper.ts +0 -1
- /package/scripts/extension-wrappers/{drizzle-orm → kysely}/extension.json +0 -0
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ Run it from the project root, for example:
|
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
58
|
../../scripts/fullsty-server-pkg.js add pg
|
|
59
|
-
../../scripts/fullsty-server-pkg.js add
|
|
59
|
+
../../scripts/fullsty-server-pkg.js add kysely
|
|
60
60
|
../../scripts/fullsty-server-pkg.js remove pg
|
|
61
61
|
```
|
|
62
62
|
|
|
@@ -75,7 +75,7 @@ The script does not manage the project-level [package.json](project-frame/packag
|
|
|
75
75
|
|
|
76
76
|
After `fullsty-server-pkg.js add <package>` you should run `npm run generate` again so the updated project server package file is copied into `generated/server/package.json`. After that, run `npm install` again inside [projects/demo1/generated/server](projects/demo1/generated/server) so the generated server gets the updated dependencies.
|
|
77
77
|
|
|
78
|
-
`fullsty-server-pkg.js add
|
|
78
|
+
`fullsty-server-pkg.js add kysely` copies [scripts/extension-wrappers/kysely/kysely-wrapper.ts](scripts/extension-wrappers/kysely/kysely-wrapper.ts) into [projects/demo1/src/server](projects/demo1/src/server). This keeps `kysely` visible as the recommended SQL style without coupling it to a specific DBMS wrapper. For PostgreSQL pooling and env handling you can add `pg` separately.
|
|
79
79
|
|
|
80
80
|
## 5) Start the generated server
|
|
81
81
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tobi2409/fullsty",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Fullsty is a modular fullstack framework starter built from three core parts: template-engine, layout and proc2rest. It provides a project frame for building fullstack applications with a clear separation of client and server code.",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "Tobias Hollstein",
|
|
@@ -1,29 +1,41 @@
|
|
|
1
|
-
// @ts-ignore pg-wrapper is supplied by the pg extension in generated projects.
|
|
2
|
-
import { getPgPool, closeAllPgPools } from "../pg/pg-wrapper";
|
|
3
|
-
|
|
4
1
|
type DriverName = "pg" | "mysql" | "sqlite";
|
|
5
2
|
|
|
6
|
-
|
|
3
|
+
interface ConnectionData {
|
|
7
4
|
connectionName?: string;
|
|
8
5
|
};
|
|
9
6
|
|
|
10
7
|
export class DbConnection {
|
|
8
|
+
private connectionWrapper?: unknown;
|
|
9
|
+
|
|
11
10
|
constructor(
|
|
12
11
|
private readonly driverName: DriverName,
|
|
13
|
-
private readonly connectionData: ConnectionData = {}
|
|
12
|
+
private readonly connectionData: ConnectionData = {}
|
|
14
13
|
) {}
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
getDriverName(): DriverName {
|
|
16
|
+
return this.driverName;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async getConnection(): Promise<unknown> {
|
|
17
20
|
if (this.driverName === "pg") {
|
|
18
|
-
|
|
21
|
+
if (!this.connectionWrapper) {
|
|
22
|
+
// @ts-ignore pg-wrapper is supplied by the pg extension in generated projects.
|
|
23
|
+
const { PgConnectionWrapper } = await import("../pg/pg-wrapper.ts");
|
|
24
|
+
this.connectionWrapper = new PgConnectionWrapper(this.connectionData.connectionName ?? "default");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return (this.connectionWrapper as any).getPgPool();
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
throw new Error(`Driver is not integrated yet: ${this.driverName}`);
|
|
22
31
|
}
|
|
23
32
|
|
|
24
|
-
async close() {
|
|
33
|
+
async close(): Promise<void> {
|
|
25
34
|
if (this.driverName === "pg") {
|
|
26
|
-
|
|
35
|
+
if (this.connectionWrapper) {
|
|
36
|
+
await (this.connectionWrapper as any).closePool();
|
|
37
|
+
}
|
|
38
|
+
|
|
27
39
|
return;
|
|
28
40
|
}
|
|
29
41
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// @ts-ignore kysely is supplied by the server package in generated projects.
|
|
2
|
+
import * as k from "kysely";
|
|
3
|
+
|
|
4
|
+
type DriverName = "pg" | "mysql" | "sqlite";
|
|
5
|
+
|
|
6
|
+
export namespace KyselyWrapper {
|
|
7
|
+
export const sql = k.sql;
|
|
8
|
+
|
|
9
|
+
export function getKysely(): typeof k {
|
|
10
|
+
return k;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function buildQueryCompiler(driverName: DriverName): k.Kysely<any> {
|
|
14
|
+
const kysely = getKysely() as any;
|
|
15
|
+
|
|
16
|
+
if (driverName === "pg") {
|
|
17
|
+
return new kysely.Kysely({
|
|
18
|
+
dialect: {
|
|
19
|
+
createAdapter: () => new kysely.PostgresAdapter(),
|
|
20
|
+
createDriver: () => new kysely.DummyDriver(),
|
|
21
|
+
createIntrospector: (db: any) => new kysely.PostgresIntrospector(db),
|
|
22
|
+
createQueryCompiler: () => new kysely.PostgresQueryCompiler(),
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
throw new Error(`Driver is not integrated yet: ${driverName}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,82 +1,73 @@
|
|
|
1
|
+
// @ts-ignore pg dependencies are supplied by the server package in generated projects.
|
|
1
2
|
import dotenv from "dotenv";
|
|
3
|
+
// @ts-ignore node types are supplied by the server package in generated projects.
|
|
2
4
|
import fs from "fs";
|
|
5
|
+
// @ts-ignore node types are supplied by the server package in generated projects.
|
|
3
6
|
import path from "path";
|
|
7
|
+
// @ts-ignore pg dependencies are supplied by the server package in generated projects.
|
|
4
8
|
import { Pool } from "pg";
|
|
9
|
+
// @ts-ignore node types are supplied by the server package in generated projects.
|
|
5
10
|
import { fileURLToPath } from "url";
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
export class PgConnectionWrapper {
|
|
13
|
+
private pool: Pool | null = null;
|
|
14
|
+
private pgEnv: Record<string, string> = {};
|
|
15
|
+
private pgEnvLoaded: boolean = false;
|
|
16
|
+
private readonly wrapperDir: string = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
private readonly envFilePath: string = path.join(this.wrapperDir, ".env.pg");
|
|
12
18
|
|
|
13
|
-
|
|
14
|
-
const prefix = connectionName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
15
|
-
return `${prefix}_${key}`;
|
|
16
|
-
}
|
|
19
|
+
constructor(private readonly connectionName: string = "default") {}
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return
|
|
21
|
+
private envKey(key: string): string {
|
|
22
|
+
const prefix = this.connectionName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
23
|
+
return `${prefix}_${key}`;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
: "";
|
|
26
|
+
private loadPgEnv(): void {
|
|
27
|
+
// only load the .env.pg file once
|
|
28
|
+
if (this.pgEnvLoaded) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
// Load .env.pg into a local cache instead of writing into process.env.
|
|
33
|
+
// This keeps the pg wrapper isolated from other wrappers such as auth,
|
|
34
|
+
// which may load their own .env files later.
|
|
35
|
+
const envFileContent = fs.existsSync(this.envFilePath)
|
|
36
|
+
? fs.readFileSync(this.envFilePath, "utf8")
|
|
37
|
+
: "";
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
key: string,
|
|
37
|
-
fallback?: string,
|
|
38
|
-
): string {
|
|
39
|
-
loadPgEnv();
|
|
40
|
-
return pgEnv[envKey(connectionName, key)] ?? fallback ?? "";
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function getPgPool(connectionName: string = "default"): Pool {
|
|
44
|
-
const existing = pools.get(connectionName);
|
|
45
|
-
if (existing) {
|
|
46
|
-
return existing;
|
|
39
|
+
this.pgEnv = dotenv.parse(envFileContent);
|
|
40
|
+
this.pgEnvLoaded = true;
|
|
47
41
|
}
|
|
48
42
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
password: getEnv(connectionName, "PGPASSWORD", "postgres"),
|
|
54
|
-
database: getEnv(connectionName, "PGDATABASE", "fullsty_demo"),
|
|
55
|
-
max: Number(getEnv(connectionName, "POOL_MAX", "10")),
|
|
56
|
-
});
|
|
43
|
+
private getEnv(key: string, fallback?: string): string {
|
|
44
|
+
this.loadPgEnv();
|
|
45
|
+
return this.pgEnv[this.envKey(key)] ?? fallback ?? "";
|
|
46
|
+
}
|
|
57
47
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
48
|
+
getPgPool(): Pool {
|
|
49
|
+
if (this.pool) {
|
|
50
|
+
return this.pool;
|
|
51
|
+
}
|
|
61
52
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
53
|
+
this.pool = new Pool({
|
|
54
|
+
host: this.getEnv("PGHOST", "127.0.0.1"),
|
|
55
|
+
port: Number(this.getEnv("PGPORT", "5432")),
|
|
56
|
+
user: this.getEnv("PGUSER", "postgres"),
|
|
57
|
+
password: this.getEnv("PGPASSWORD", "postgres"),
|
|
58
|
+
database: this.getEnv("PGDATABASE", "fullsty_demo"),
|
|
59
|
+
max: Number(this.getEnv("POOL_MAX", "10"))
|
|
60
|
+
});
|
|
66
61
|
|
|
67
|
-
|
|
68
|
-
return;
|
|
62
|
+
return this.pool;
|
|
69
63
|
}
|
|
70
64
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
export async function closeAllPgPools(): Promise<void> {
|
|
76
|
-
const all = [...pools.entries()];
|
|
65
|
+
async closePool(): Promise<void> {
|
|
66
|
+
if (!this.pool) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
77
69
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
pools.delete(name);
|
|
70
|
+
await this.pool.end();
|
|
71
|
+
this.pool = null;
|
|
81
72
|
}
|
|
82
|
-
}
|
|
73
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { sql } from "drizzle-orm";
|
|
File without changes
|