@devbro/pashmak 0.1.48 → 0.1.51
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/DatabaseServiceProvider.d.mts +5 -2
- package/dist/DatabaseServiceProvider.mjs +10 -3
- package/dist/DatabaseServiceProvider.mjs.map +1 -1
- package/dist/app/console/StartCommand.d.mts +1 -0
- package/dist/app/console/StartCommand.mjs +5 -2
- package/dist/app/console/StartCommand.mjs.map +1 -1
- package/dist/app/console/generate/GenerateApiDocsCommand.d.mts +34 -2
- package/dist/app/console/generate/GenerateApiDocsCommand.mjs +183 -100
- package/dist/app/console/generate/GenerateApiDocsCommand.mjs.map +1 -1
- package/dist/app/console/project/base_project/src/config/storages.ts.tpl +2 -2
- package/dist/bin/DatabaseServiceProvider.cjs +12 -2
- package/dist/bin/app/console/DefaultCommand.cjs +136 -69
- package/dist/bin/app/console/KeyGenerateCommand.cjs +136 -69
- package/dist/bin/app/console/StartCommand.cjs +141 -71
- package/dist/bin/app/console/generate/GenerateApiDocsCommand.cjs +320 -170
- package/dist/bin/app/console/generate/GenerateControllerCommand.cjs +136 -69
- package/dist/bin/app/console/generate/index.cjs +320 -170
- package/dist/bin/app/console/index.cjs +325 -172
- package/dist/bin/app/console/migrate/GenerateMigrateCommand.cjs +136 -69
- package/dist/bin/app/console/migrate/MigrateCommand.cjs +136 -69
- package/dist/bin/app/console/migrate/MigrateRollbackCommand.cjs +136 -69
- package/dist/bin/app/console/migrate/index.cjs +136 -69
- package/dist/bin/app/console/queue/GenerateQueueMigrateCommand.cjs +136 -69
- package/dist/bin/cache.cjs +136 -69
- package/dist/bin/facades.cjs +136 -69
- package/dist/bin/factories.cjs +136 -69
- package/dist/bin/http.cjs +136 -69
- package/dist/bin/index.cjs +338 -178
- package/dist/bin/middlewares.cjs +136 -69
- package/dist/bin/queue.cjs +136 -69
- package/dist/bin/router.cjs +3 -5
- package/dist/facades.d.mts +6 -6
- package/dist/facades.mjs +97 -58
- package/dist/facades.mjs.map +1 -1
- package/dist/factories.mjs +45 -2
- package/dist/factories.mjs.map +1 -1
- package/dist/http.mjs +1 -1
- package/dist/http.mjs.map +1 -1
- package/dist/queue.d.mts +1 -1
- package/dist/queue.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { Middleware, Request, Response } from '@devbro/neko-router';
|
|
2
|
+
import { SqliteConfig, Connection } from '@devbro/neko-sql';
|
|
2
3
|
import { PoolConfig } from 'pg';
|
|
3
|
-
import { Connection } from '@devbro/neko-sql';
|
|
4
4
|
|
|
5
5
|
declare class DatabaseServiceProvider extends Middleware {
|
|
6
6
|
call(req: Request, res: Response, next: () => Promise<void>): Promise<void>;
|
|
7
7
|
private static instance;
|
|
8
8
|
register(): Promise<void>;
|
|
9
9
|
static getInstance(): DatabaseServiceProvider;
|
|
10
|
-
getConnection(db_config:
|
|
10
|
+
getConnection(db_config: {
|
|
11
|
+
provider: string;
|
|
12
|
+
config: PoolConfig | SqliteConfig;
|
|
13
|
+
}): Connection;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
export { DatabaseServiceProvider };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
3
|
import { Middleware } from "@devbro/neko-router";
|
|
4
|
-
import { PostgresqlConnection } from "@devbro/neko-sql";
|
|
4
|
+
import { PostgresqlConnection, SqliteConnection } from "@devbro/neko-sql";
|
|
5
5
|
import { BaseModel } from "@devbro/neko-orm";
|
|
6
6
|
import { ctx } from "@devbro/neko-context";
|
|
7
7
|
import { config } from "@devbro/neko-config";
|
|
@@ -49,8 +49,15 @@ class DatabaseServiceProvider extends Middleware {
|
|
|
49
49
|
return DatabaseServiceProvider.instance;
|
|
50
50
|
}
|
|
51
51
|
getConnection(db_config) {
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
if (db_config.provider === "postgresql") {
|
|
53
|
+
const conn = new PostgresqlConnection(db_config.config);
|
|
54
|
+
return conn;
|
|
55
|
+
}
|
|
56
|
+
if (db_config.provider === "sqlite") {
|
|
57
|
+
const conn = new SqliteConnection(db_config.config);
|
|
58
|
+
return conn;
|
|
59
|
+
}
|
|
60
|
+
throw new Error(`Unsupported database provider: ${db_config.provider}`);
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
63
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/DatabaseServiceProvider.mts"],"sourcesContent":["import { Middleware } from \"@devbro/neko-router\";\nimport { Request, Response } from \"@devbro/neko-router\";\nimport { PostgresqlConnection } from \"@devbro/neko-sql\";\nimport { PoolConfig } from \"pg\";\nimport { Connection } from \"@devbro/neko-sql\";\nimport { BaseModel } from \"@devbro/neko-orm\";\nimport { ctx } from \"@devbro/neko-context\";\nimport { config } from \"@devbro/neko-config\";\nimport { Global } from \"./global.mjs\";\n\nexport class DatabaseServiceProvider extends Middleware {\n async call(\n req: Request,\n res: Response,\n next: () => Promise<void>,\n ): Promise<void> {\n const db_configs: Record<string,
|
|
1
|
+
{"version":3,"sources":["../src/DatabaseServiceProvider.mts"],"sourcesContent":["import { Middleware } from \"@devbro/neko-router\";\nimport { Request, Response } from \"@devbro/neko-router\";\nimport { PostgresqlConnection, SqliteConnection, SqliteConfig } from \"@devbro/neko-sql\";\nimport { PoolConfig } from \"pg\";\nimport { Connection } from \"@devbro/neko-sql\";\nimport { BaseModel } from \"@devbro/neko-orm\";\nimport { ctx } from \"@devbro/neko-context\";\nimport { config } from \"@devbro/neko-config\";\nimport { Global } from \"./global.mjs\";\n\nexport class DatabaseServiceProvider extends Middleware {\n async call(\n req: Request,\n res: Response,\n next: () => Promise<void>,\n ): Promise<void> {\n const db_configs: Record<string, { provider: string; config: PoolConfig | SqliteConfig }> =\n config.get(\"databases\");\n\n const conns = [];\n try {\n for (const [name, db_config] of Object.entries(db_configs)) {\n const conn = await this.getConnection(db_config);\n ctx().set([\"database\", name], conn);\n conns.push(conn);\n }\n BaseModel.setConnection(() => {\n const key = [\"database\", \"default\"];\n let rc: Connection | undefined;\n\n if (ctx.isActive()) {\n rc = ctx().get<Connection>(key);\n } else if (Global.has(key)) {\n rc = Global.get<Connection>(key);\n } else {\n rc = this.getConnection(db_configs[\"default\"]);\n Global.set(key, rc);\n }\n\n return rc!;\n });\n await next();\n } finally {\n for (const conn of conns) {\n await conn.disconnect();\n }\n }\n }\n\n private static instance: DatabaseServiceProvider;\n\n async register(): Promise<void> {}\n\n static getInstance(): DatabaseServiceProvider {\n if (!DatabaseServiceProvider.instance) {\n DatabaseServiceProvider.instance = new DatabaseServiceProvider();\n }\n return DatabaseServiceProvider.instance;\n }\n\n getConnection(db_config: {\n provider: string;\n config: PoolConfig | SqliteConfig;\n }): Connection {\n if (db_config.provider === \"postgresql\") {\n const conn = new PostgresqlConnection(db_config.config as PoolConfig);\n return conn;\n }\n\n if (db_config.provider === \"sqlite\") {\n const conn = new SqliteConnection(db_config.config as SqliteConfig);\n return conn;\n }\n\n throw new Error(`Unsupported database provider: ${db_config.provider}`);\n }\n}\n"],"mappings":";;AAAA,SAAS,kBAAkB;AAE3B,SAAS,sBAAsB,wBAAsC;AAGrE,SAAS,iBAAiB;AAC1B,SAAS,WAAW;AACpB,SAAS,cAAc;AACvB,SAAS,cAAc;AAEhB,MAAM,gCAAgC,WAAW;AAAA,EAVxD,OAUwD;AAAA;AAAA;AAAA,EACtD,MAAM,KACJ,KACA,KACA,MACe;AACf,UAAM,aACJ,OAAO,IAAI,WAAW;AAExB,UAAM,QAAQ,CAAC;AACf,QAAI;AACF,iBAAW,CAAC,MAAM,SAAS,KAAK,OAAO,QAAQ,UAAU,GAAG;AAC1D,cAAM,OAAO,MAAM,KAAK,cAAc,SAAS;AAC/C,YAAI,EAAE,IAAI,CAAC,YAAY,IAAI,GAAG,IAAI;AAClC,cAAM,KAAK,IAAI;AAAA,MACjB;AACA,gBAAU,cAAc,MAAM;AAC5B,cAAM,MAAM,CAAC,YAAY,SAAS;AAClC,YAAI;AAEJ,YAAI,IAAI,SAAS,GAAG;AAClB,eAAK,IAAI,EAAE,IAAgB,GAAG;AAAA,QAChC,WAAW,OAAO,IAAI,GAAG,GAAG;AAC1B,eAAK,OAAO,IAAgB,GAAG;AAAA,QACjC,OAAO;AACL,eAAK,KAAK,cAAc,WAAW,SAAS,CAAC;AAC7C,iBAAO,IAAI,KAAK,EAAE;AAAA,QACpB;AAEA,eAAO;AAAA,MACT,CAAC;AACD,YAAM,KAAK;AAAA,IACb,UAAE;AACA,iBAAW,QAAQ,OAAO;AACxB,cAAM,KAAK,WAAW;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,OAAe;AAAA,EAEf,MAAM,WAA0B;AAAA,EAAC;AAAA,EAEjC,OAAO,cAAuC;AAC5C,QAAI,CAAC,wBAAwB,UAAU;AACrC,8BAAwB,WAAW,IAAI,wBAAwB;AAAA,IACjE;AACA,WAAO,wBAAwB;AAAA,EACjC;AAAA,EAEA,cAAc,WAGC;AACb,QAAI,UAAU,aAAa,cAAc;AACvC,YAAM,OAAO,IAAI,qBAAqB,UAAU,MAAoB;AACpE,aAAO;AAAA,IACT;AAEA,QAAI,UAAU,aAAa,UAAU;AACnC,YAAM,OAAO,IAAI,iBAAiB,UAAU,MAAsB;AAClE,aAAO;AAAA,IACT;AAEA,UAAM,IAAI,MAAM,kCAAkC,UAAU,QAAQ,EAAE;AAAA,EACxE;AACF;","names":[]}
|
|
@@ -9,12 +9,15 @@ class StartCommand extends Command {
|
|
|
9
9
|
__name(this, "StartCommand");
|
|
10
10
|
}
|
|
11
11
|
scheduler = Option.Boolean(`--scheduler`, false);
|
|
12
|
+
cron = Option.Boolean(`--cron`, false);
|
|
12
13
|
http = Option.Boolean(`--http`, false);
|
|
13
14
|
queue = Option.Boolean(`--queue`, false);
|
|
14
15
|
all = Option.Boolean("--all", false);
|
|
15
16
|
static paths = [[`start`]];
|
|
16
17
|
async execute() {
|
|
17
|
-
if ([this.all, this.http, this.scheduler, this.queue].filter(
|
|
18
|
+
if ([this.all, this.http, this.scheduler || this.cron, this.queue].filter(
|
|
19
|
+
(x) => x
|
|
20
|
+
).length == 0) {
|
|
18
21
|
this.context.stdout.write(
|
|
19
22
|
`No service was selected. please check -h for details
|
|
20
23
|
`
|
|
@@ -24,7 +27,7 @@ class StartCommand extends Command {
|
|
|
24
27
|
logger().info(`Starting Server
|
|
25
28
|
`);
|
|
26
29
|
PostgresqlConnection.defaults.idleTimeoutMillis = 1e4;
|
|
27
|
-
if (this.scheduler || this.all) {
|
|
30
|
+
if (this.scheduler || this.cron || this.all) {
|
|
28
31
|
logger().info(`starting scheduler
|
|
29
32
|
`);
|
|
30
33
|
scheduler().start();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/app/console/StartCommand.mts"],"sourcesContent":["import { Command, Option } from \"clipanion\";\nimport { config } from \"@devbro/neko-config\";\n\nimport { cli, httpServer, logger, scheduler, queue } from \"../../facades.mjs\";\nimport { PostgresqlConnection } from \"@devbro/neko-sql\";\n\nexport class StartCommand extends Command {\n scheduler = Option.Boolean(`--scheduler`, false);\n http = Option.Boolean(`--http`, false);\n queue = Option.Boolean(`--queue`, false);\n all = Option.Boolean(\"--all\", false);\n static paths = [[`start`]];\n\n async execute() {\n if (\n [this.all, this.http, this.scheduler, this.queue].filter((x) => x)
|
|
1
|
+
{"version":3,"sources":["../../../src/app/console/StartCommand.mts"],"sourcesContent":["import { Command, Option } from \"clipanion\";\nimport { config } from \"@devbro/neko-config\";\n\nimport { cli, httpServer, logger, scheduler, queue } from \"../../facades.mjs\";\nimport { PostgresqlConnection } from \"@devbro/neko-sql\";\n\nexport class StartCommand extends Command {\n scheduler = Option.Boolean(`--scheduler`, false);\n cron = Option.Boolean(`--cron`, false);\n http = Option.Boolean(`--http`, false);\n queue = Option.Boolean(`--queue`, false);\n all = Option.Boolean(\"--all\", false);\n static paths = [[`start`]];\n\n async execute() {\n if (\n [this.all, this.http, this.scheduler || this.cron, this.queue].filter(\n (x) => x,\n ).length == 0\n ) {\n this.context.stdout.write(\n `No service was selected. please check -h for details\\n`,\n );\n return;\n }\n\n logger().info(`Starting Server\\n`);\n\n PostgresqlConnection.defaults.idleTimeoutMillis = 10000;\n\n if (this.scheduler || this.cron || this.all) {\n logger().info(`starting scheduler\\n`);\n scheduler().start();\n }\n\n if (this.queue || this.all) {\n const config_queues = config.get(\"queues\");\n for (const [name, conf] of Object.entries(config_queues)) {\n queue(name).start();\n }\n }\n\n if (this.http || this.all) {\n const server = httpServer();\n await server.listen(config.get(\"port\"), () => {\n logger().info(\n \"Server is running on http://localhost:\" + config.get(\"port\"),\n );\n });\n }\n }\n}\n\ncli().register(StartCommand);\n"],"mappings":";;AAAA,SAAS,SAAS,cAAc;AAChC,SAAS,cAAc;AAEvB,SAAS,KAAK,YAAY,QAAQ,WAAW,aAAa;AAC1D,SAAS,4BAA4B;AAE9B,MAAM,qBAAqB,QAAQ;AAAA,EAN1C,OAM0C;AAAA;AAAA;AAAA,EACxC,YAAY,OAAO,QAAQ,eAAe,KAAK;AAAA,EAC/C,OAAO,OAAO,QAAQ,UAAU,KAAK;AAAA,EACrC,OAAO,OAAO,QAAQ,UAAU,KAAK;AAAA,EACrC,QAAQ,OAAO,QAAQ,WAAW,KAAK;AAAA,EACvC,MAAM,OAAO,QAAQ,SAAS,KAAK;AAAA,EACnC,OAAO,QAAQ,CAAC,CAAC,OAAO,CAAC;AAAA,EAEzB,MAAM,UAAU;AACd,QACE,CAAC,KAAK,KAAK,KAAK,MAAM,KAAK,aAAa,KAAK,MAAM,KAAK,KAAK,EAAE;AAAA,MAC7D,CAAC,MAAM;AAAA,IACT,EAAE,UAAU,GACZ;AACA,WAAK,QAAQ,OAAO;AAAA,QAClB;AAAA;AAAA,MACF;AACA;AAAA,IACF;AAEA,WAAO,EAAE,KAAK;AAAA,CAAmB;AAEjC,yBAAqB,SAAS,oBAAoB;AAElD,QAAI,KAAK,aAAa,KAAK,QAAQ,KAAK,KAAK;AAC3C,aAAO,EAAE,KAAK;AAAA,CAAsB;AACpC,gBAAU,EAAE,MAAM;AAAA,IACpB;AAEA,QAAI,KAAK,SAAS,KAAK,KAAK;AAC1B,YAAM,gBAAgB,OAAO,IAAI,QAAQ;AACzC,iBAAW,CAAC,MAAM,IAAI,KAAK,OAAO,QAAQ,aAAa,GAAG;AACxD,cAAM,IAAI,EAAE,MAAM;AAAA,MACpB;AAAA,IACF;AAEA,QAAI,KAAK,QAAQ,KAAK,KAAK;AACzB,YAAM,SAAS,WAAW;AAC1B,YAAM,OAAO,OAAO,OAAO,IAAI,MAAM,GAAG,MAAM;AAC5C,eAAO,EAAE;AAAA,UACP,2CAA2C,OAAO,IAAI,MAAM;AAAA,QAC9D;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,IAAI,EAAE,SAAS,YAAY;","names":[]}
|
|
@@ -4,9 +4,41 @@ import { Command } from 'clipanion';
|
|
|
4
4
|
declare class GenerateApiDocsCommand extends Command {
|
|
5
5
|
static paths: string[][];
|
|
6
6
|
static usage: clipanion.Usage;
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
subcommand: string | undefined;
|
|
8
|
+
output: string | undefined;
|
|
9
|
+
config: string | undefined;
|
|
10
|
+
execute(): Promise<number>;
|
|
11
|
+
private executeGenerateFromRoutes;
|
|
12
|
+
private executeGenerateBase;
|
|
13
|
+
private executeMergeFiles;
|
|
9
14
|
private extractParameters;
|
|
15
|
+
private generateFromRoutes;
|
|
16
|
+
getBaseOpenApiSpec(): {
|
|
17
|
+
openapi: string;
|
|
18
|
+
info: {
|
|
19
|
+
title: string;
|
|
20
|
+
version: string;
|
|
21
|
+
description: string;
|
|
22
|
+
};
|
|
23
|
+
servers: {
|
|
24
|
+
url: string;
|
|
25
|
+
description: string;
|
|
26
|
+
}[];
|
|
27
|
+
components: {
|
|
28
|
+
securitySchemes: {
|
|
29
|
+
bearerAuth: {
|
|
30
|
+
type: string;
|
|
31
|
+
scheme: string;
|
|
32
|
+
bearerFormat: string;
|
|
33
|
+
description: string;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
security: {
|
|
38
|
+
bearerAuth: never[];
|
|
39
|
+
}[];
|
|
40
|
+
paths: Record<string, any>;
|
|
41
|
+
};
|
|
10
42
|
}
|
|
11
43
|
|
|
12
44
|
export { GenerateApiDocsCommand };
|
|
@@ -10,139 +10,150 @@ class GenerateApiDocsCommand extends Command {
|
|
|
10
10
|
static {
|
|
11
11
|
__name(this, "GenerateApiDocsCommand");
|
|
12
12
|
}
|
|
13
|
-
static paths = [
|
|
14
|
-
[`make`, `apidocs`],
|
|
15
|
-
[`generate`, `apidocs`]
|
|
16
|
-
];
|
|
13
|
+
static paths = [[`generate`, `apidocs`]];
|
|
17
14
|
static usage = Command.Usage({
|
|
18
15
|
category: `Generate`,
|
|
19
16
|
description: `Generate OpenAPI documentation from routes`,
|
|
20
17
|
details: `
|
|
21
|
-
This command generates OpenAPI 3.0 specification documentation by analyzing
|
|
18
|
+
This command utility generates OpenAPI 3.0 specification documentation by analyzing
|
|
22
19
|
your application's routes and merging with example files.
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
- Response schemas
|
|
29
|
-
|
|
30
|
-
The command will merge files specified in config.api_docs.merge_files
|
|
31
|
-
and output the final documentation to config.api_docs.output.
|
|
21
|
+
Subcommands:
|
|
22
|
+
- generate-from-routes: Generate OpenAPI spec from registered routes
|
|
23
|
+
- generate-base: Generate base OpenAPI specification structure
|
|
24
|
+
- merge-files: Merge multiple OpenAPI files into final documentation
|
|
32
25
|
|
|
26
|
+
|
|
33
27
|
This command depends on config data. make sure your default config contains the following:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
28
|
+
|
|
29
|
+
\`\`\`
|
|
30
|
+
api_docs: {
|
|
31
|
+
|
|
32
|
+
merge_files: [
|
|
33
|
+
|
|
34
|
+
path.join(__dirname, '../..', 'private', 'openapi_base.json'),
|
|
35
|
+
|
|
36
|
+
path.join(__dirname, '../..', 'private', 'openapi_from_routes.json'),
|
|
37
|
+
|
|
38
|
+
path.join(__dirname, '../..', 'private', 'openapi_from_tests.json'),
|
|
39
|
+
|
|
40
|
+
path.join(__dirname, '../..', 'private', 'openapi_other_user_changes.json'),
|
|
41
|
+
|
|
42
|
+
],
|
|
43
|
+
|
|
44
|
+
output: path.join(__dirname, '../..', 'public', 'openapi.json'),
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
\`\`\`
|
|
42
49
|
`,
|
|
43
|
-
examples: [
|
|
50
|
+
examples: [
|
|
51
|
+
[
|
|
52
|
+
`Generate from routes`,
|
|
53
|
+
`$0 generate apidocs generate-from-routes --output path/to/output.json`
|
|
54
|
+
],
|
|
55
|
+
[
|
|
56
|
+
`Generate base spec`,
|
|
57
|
+
`$0 generate apidocs generate-base --output path/to/output.json`
|
|
58
|
+
],
|
|
59
|
+
[`Merge files`, `$0 generate apidocs merge-files`],
|
|
60
|
+
[`Show help`, `$0 generate apidocs --help`]
|
|
61
|
+
]
|
|
44
62
|
});
|
|
45
|
-
|
|
46
|
-
|
|
63
|
+
subcommand = Option.String({ required: false });
|
|
64
|
+
output = Option.String(`--output,-o`, {
|
|
65
|
+
description: `Output file path for generated documentation`
|
|
66
|
+
});
|
|
67
|
+
config = Option.String(`--config,-c`, {
|
|
68
|
+
description: `Path in config to get details from (default: api_docs)`,
|
|
69
|
+
required: false
|
|
47
70
|
});
|
|
48
71
|
async execute() {
|
|
49
|
-
if (this.
|
|
72
|
+
if (!this.subcommand) {
|
|
50
73
|
this.context.stdout.write(
|
|
51
74
|
this.constructor.usage?.toString() || "No help available\n"
|
|
52
75
|
);
|
|
53
76
|
return 0;
|
|
54
77
|
}
|
|
55
|
-
|
|
56
|
-
|
|
78
|
+
switch (this.subcommand) {
|
|
79
|
+
case "generate-from-routes":
|
|
80
|
+
return await this.executeGenerateFromRoutes();
|
|
81
|
+
case "generate-base":
|
|
82
|
+
return await this.executeGenerateBase();
|
|
83
|
+
case "merge-files":
|
|
84
|
+
return await this.executeMergeFiles();
|
|
85
|
+
default:
|
|
86
|
+
this.context.stderr.write(`Unknown subcommand: ${this.subcommand}
|
|
57
87
|
`);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
title: "API Documentation",
|
|
63
|
-
version: "1.0.0",
|
|
64
|
-
description: "Auto-generated API documentation"
|
|
65
|
-
},
|
|
66
|
-
servers: [
|
|
67
|
-
{
|
|
68
|
-
url: "/",
|
|
69
|
-
description: "Local server"
|
|
70
|
-
}
|
|
71
|
-
],
|
|
72
|
-
paths: {}
|
|
73
|
-
};
|
|
74
|
-
for (const route of routes) {
|
|
75
|
-
const routePath = route.path;
|
|
76
|
-
const openApiPath = routePath.replace(/:([a-zA-Z0-9_]+)/g, "{$1}");
|
|
77
|
-
if (!openApiSpec.paths[openApiPath]) {
|
|
78
|
-
openApiSpec.paths[openApiPath] = {};
|
|
79
|
-
}
|
|
80
|
-
for (const method of route.methods) {
|
|
81
|
-
const lowerMethod = method.toLowerCase();
|
|
82
|
-
if (lowerMethod === "head") {
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
openApiSpec.paths[openApiPath][lowerMethod] = {
|
|
86
|
-
summary: `${method} ${routePath}`,
|
|
87
|
-
description: `Endpoint for ${method} ${routePath}`,
|
|
88
|
-
parameters: this.extractParameters(routePath),
|
|
89
|
-
responses: {
|
|
90
|
-
"200": {
|
|
91
|
-
description: "Successful response",
|
|
92
|
-
content: {
|
|
93
|
-
"application/json": {
|
|
94
|
-
schema: {
|
|
95
|
-
type: "object"
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
"500": {
|
|
101
|
-
description: "Internal server error"
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
if (["post", "put", "patch"].includes(lowerMethod)) {
|
|
106
|
-
openApiSpec.paths[openApiPath][lowerMethod].requestBody = {
|
|
107
|
-
required: true,
|
|
108
|
-
content: {
|
|
109
|
-
"application/json": {
|
|
110
|
-
schema: {
|
|
111
|
-
type: "object"
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
}
|
|
88
|
+
this.context.stdout.write(
|
|
89
|
+
this.constructor.usage?.toString() || "No help available\n"
|
|
90
|
+
);
|
|
91
|
+
return 1;
|
|
118
92
|
}
|
|
119
|
-
|
|
120
|
-
|
|
93
|
+
}
|
|
94
|
+
async executeGenerateFromRoutes() {
|
|
95
|
+
this.context.stdout.write(
|
|
96
|
+
`Generating OpenAPI documentation from routes...
|
|
97
|
+
`
|
|
98
|
+
);
|
|
99
|
+
const openApiSpec = this.generateFromRoutes();
|
|
100
|
+
const outputPath = this.output || path.join(config.get("private_path"), "openapi_from_routes.json");
|
|
101
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
121
102
|
await fs.writeFile(
|
|
122
103
|
outputPath,
|
|
123
104
|
JSON.stringify(openApiSpec, null, 2),
|
|
124
105
|
"utf-8"
|
|
125
106
|
);
|
|
126
107
|
this.context.stdout.write(
|
|
127
|
-
`OpenAPI documentation generated at: ${outputPath}
|
|
108
|
+
`OpenAPI routes documentation generated at: ${outputPath}
|
|
128
109
|
`
|
|
129
110
|
);
|
|
130
|
-
this.context.stdout.write(
|
|
111
|
+
this.context.stdout.write(
|
|
112
|
+
`Total routes documented: ${Object.keys(openApiSpec.paths).length}
|
|
113
|
+
`
|
|
114
|
+
);
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
async executeGenerateBase() {
|
|
118
|
+
this.context.stdout.write(`Generating base OpenAPI specification...
|
|
131
119
|
`);
|
|
132
|
-
|
|
120
|
+
const baseSpec = this.getBaseOpenApiSpec();
|
|
121
|
+
const outputPath = this.output || path.join(config.get("private_path"), "openapi_base.json");
|
|
122
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
123
|
+
await fs.writeFile(outputPath, JSON.stringify(baseSpec, null, 2), "utf-8");
|
|
124
|
+
this.context.stdout.write(
|
|
125
|
+
`Base OpenAPI specification generated at: ${outputPath}
|
|
126
|
+
`
|
|
127
|
+
);
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
async executeMergeFiles() {
|
|
131
|
+
this.context.stdout.write(`Merging OpenAPI files...
|
|
132
|
+
`);
|
|
133
|
+
let configPath = this.config || "api_docs";
|
|
134
|
+
const files_to_merge = config.get(`${configPath}.merge_files`);
|
|
133
135
|
let final_api_docs = {};
|
|
134
|
-
for (
|
|
135
|
-
|
|
136
|
-
|
|
136
|
+
for (const file_path of files_to_merge) {
|
|
137
|
+
try {
|
|
138
|
+
const file_json = JSON.parse(await fs.readFile(file_path, "utf8"));
|
|
139
|
+
final_api_docs = Arr.deepMerge(final_api_docs, file_json);
|
|
140
|
+
this.context.stdout.write(` Merged: ${file_path}
|
|
141
|
+
`);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
this.context.stderr.write(
|
|
144
|
+
` Warning: Could not read ${file_path}: ${error.message}
|
|
145
|
+
`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
137
148
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
);
|
|
149
|
+
const outputPath = this.output || config.get(`${configPath}.output`);
|
|
150
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
151
|
+
await fs.writeFile(outputPath, JSON.stringify(final_api_docs, null, 2));
|
|
142
152
|
this.context.stdout.write(
|
|
143
|
-
`
|
|
153
|
+
`Final OpenAPI document written to: ${outputPath}
|
|
144
154
|
`
|
|
145
155
|
);
|
|
156
|
+
return 0;
|
|
146
157
|
}
|
|
147
158
|
extractParameters(routePath) {
|
|
148
159
|
const paramRegex = /:([a-zA-Z0-9_]+)/g;
|
|
@@ -161,6 +172,78 @@ class GenerateApiDocsCommand extends Command {
|
|
|
161
172
|
}
|
|
162
173
|
return parameters;
|
|
163
174
|
}
|
|
175
|
+
generateFromRoutes() {
|
|
176
|
+
const openApiSpec = {
|
|
177
|
+
paths: {}
|
|
178
|
+
};
|
|
179
|
+
const routes = router().routes;
|
|
180
|
+
for (const route of routes) {
|
|
181
|
+
const routePath = route.path;
|
|
182
|
+
const openApiPath = routePath.replace(/\/$/g, "");
|
|
183
|
+
if (!openApiSpec.paths[openApiPath]) {
|
|
184
|
+
openApiSpec.paths[openApiPath] = {};
|
|
185
|
+
}
|
|
186
|
+
for (const method of route.methods) {
|
|
187
|
+
const lowerMethod = method.toLowerCase();
|
|
188
|
+
if (lowerMethod === "head") {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
openApiSpec.paths[openApiPath][lowerMethod] = {
|
|
192
|
+
summary: `${routePath}`,
|
|
193
|
+
description: `Endpoint for ${method} ${routePath}`,
|
|
194
|
+
security: [],
|
|
195
|
+
parameters: this.extractParameters(routePath),
|
|
196
|
+
responses: {}
|
|
197
|
+
};
|
|
198
|
+
if (["post", "put", "patch"].includes(lowerMethod)) {
|
|
199
|
+
openApiSpec.paths[openApiPath][lowerMethod].requestBody = {
|
|
200
|
+
required: true,
|
|
201
|
+
content: {
|
|
202
|
+
"application/json": {
|
|
203
|
+
schema: {
|
|
204
|
+
type: "object"
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return openApiSpec;
|
|
213
|
+
}
|
|
214
|
+
getBaseOpenApiSpec() {
|
|
215
|
+
const openApiSpec = {
|
|
216
|
+
openapi: "3.0.0",
|
|
217
|
+
info: {
|
|
218
|
+
title: "API Documentation",
|
|
219
|
+
version: "1.0.0",
|
|
220
|
+
description: "Auto-generated API documentation"
|
|
221
|
+
},
|
|
222
|
+
servers: [
|
|
223
|
+
{
|
|
224
|
+
url: "/",
|
|
225
|
+
description: "Local server"
|
|
226
|
+
}
|
|
227
|
+
],
|
|
228
|
+
components: {
|
|
229
|
+
securitySchemes: {
|
|
230
|
+
bearerAuth: {
|
|
231
|
+
type: "http",
|
|
232
|
+
scheme: "bearer",
|
|
233
|
+
bearerFormat: "JWT",
|
|
234
|
+
description: "JWT token authentication"
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
security: [
|
|
239
|
+
{
|
|
240
|
+
bearerAuth: []
|
|
241
|
+
}
|
|
242
|
+
],
|
|
243
|
+
paths: {}
|
|
244
|
+
};
|
|
245
|
+
return openApiSpec;
|
|
246
|
+
}
|
|
164
247
|
}
|
|
165
248
|
cli().register(GenerateApiDocsCommand);
|
|
166
249
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/app/console/generate/GenerateApiDocsCommand.mts"],"sourcesContent":["import { cli, router } from \"../../../facades.mjs\";\nimport { Command, Option } from \"clipanion\";\nimport path from \"path\";\nimport * as fs from \"fs/promises\";\nimport { config } from \"../../../config.mjs\";\nimport { Arr } from \"@devbro/neko-helper\";\n\nexport class GenerateApiDocsCommand extends Command {\n static paths = [\n [`make`, `apidocs`],\n [`generate`, `apidocs`],\n ];\n\n static usage = Command.Usage({\n category: `Generate`,\n description: `Generate OpenAPI documentation from routes`,\n details: `\n This command generates OpenAPI 3.0 specification documentation by analyzing\n your application's routes and merging with example files.\n \n The generated documentation includes:\n - All registered routes with their HTTP methods\n - Path parameters extracted from route definitions\n - Request body schemas for POST, PUT, and PATCH methods\n - Response schemas\n \n The command will merge files specified in config.api_docs.merge_files\n and output the final documentation to config.api_docs.output.\n\n This command depends on config data. make sure your default config contains the following:\n api_docs: {\n merge_files: [\n path.join(__dirname, '../..', 'private', 'openapi_examples.json'),\n path.join(__dirname, '../..', 'private', 'openapi_base.json'),\n path.join(__dirname, '../..', 'private', 'openapi_user_changes.json'),\n ],\n output: path.join(__dirname, '../..', 'private', 'openapi.json'),\n }\n `,\n examples: [[`Generate API documentation`, `$0 generate apidocs`]],\n });\n\n help = Option.Boolean(`--help,-h`, false, {\n description: `Show help message for this command`,\n });\n\n async execute() {\n if (this.help) {\n this.context.stdout.write(\n this.constructor.usage?.toString() || \"No help available\\n\",\n );\n return 0;\n }\n\n const rootDir = process.cwd();\n\n this.context.stdout.write(`Generating OpenAPI documentation...\\n`);\n\n // Get all routes from the router\n const routes = router().routes;\n\n // Generate OpenAPI 3.0 specification\n const openApiSpec = {\n openapi: \"3.0.0\",\n info: {\n title: \"API Documentation\",\n version: \"1.0.0\",\n description: \"Auto-generated API documentation\",\n },\n servers: [\n {\n url: \"/\",\n description: \"Local server\",\n },\n ],\n paths: {} as Record<string, any>,\n };\n\n // Process each route\n for (const route of routes) {\n const routePath = route.path;\n // Convert route path to OpenAPI format (e.g., /api/:id -> /api/{id})\n const openApiPath = routePath.replace(/:([a-zA-Z0-9_]+)/g, \"{$1}\");\n\n if (!openApiSpec.paths[openApiPath]) {\n openApiSpec.paths[openApiPath] = {};\n }\n\n // Add each HTTP method for this route\n for (const method of route.methods) {\n const lowerMethod = method.toLowerCase();\n\n // Skip HEAD as it's usually auto-generated\n if (lowerMethod === \"head\") {\n continue;\n }\n\n openApiSpec.paths[openApiPath][lowerMethod] = {\n summary: `${method} ${routePath}`,\n description: `Endpoint for ${method} ${routePath}`,\n parameters: this.extractParameters(routePath),\n responses: {\n \"200\": {\n description: \"Successful response\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n },\n },\n },\n },\n \"500\": {\n description: \"Internal server error\",\n },\n },\n };\n\n // Add request body for POST, PUT, PATCH\n if ([\"post\", \"put\", \"patch\"].includes(lowerMethod)) {\n openApiSpec.paths[openApiPath][lowerMethod].requestBody = {\n required: true,\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n },\n },\n },\n };\n }\n }\n }\n\n // Ensure public directory exists\n await fs.mkdir(config.get(\"private_path\"), { recursive: true });\n\n // Write the OpenAPI spec to public/openapi.json\n const outputPath = path.join(config.get(\"private_path\"), \"openapi.json\");\n await fs.writeFile(\n outputPath,\n JSON.stringify(openApiSpec, null, 2),\n \"utf-8\",\n );\n\n this.context.stdout.write(\n `OpenAPI documentation generated at: ${outputPath}\\n`,\n );\n this.context.stdout.write(`Total routes documented: ${routes.length}\\n`);\n\n let files_to_merge: string[] = config.get(\"api_docs.merge_files\");\n let final_api_docs = {};\n for (let file_path of files_to_merge) {\n let file_json = JSON.parse(await fs.readFile(file_path, \"utf8\"));\n final_api_docs = Arr.deepMerge(final_api_docs, file_json);\n }\n\n await fs.writeFile(\n config.get(\"api_docs.output\"),\n JSON.stringify(final_api_docs, null, 2),\n );\n\n this.context.stdout.write(\n `wrote final open api document to : ${config.get(\"api_docs.output\")}\\n`,\n );\n }\n\n private extractParameters(routePath: string): any[] {\n const paramRegex = /:([a-zA-Z0-9_]+)/g;\n const parameters: any[] = [];\n let match;\n\n while ((match = paramRegex.exec(routePath)) !== null) {\n parameters.push({\n name: match[1],\n in: \"path\",\n required: true,\n schema: {\n type: \"string\",\n },\n description: `Path parameter ${match[1]}`,\n });\n }\n\n return parameters;\n }\n}\n\ncli().register(GenerateApiDocsCommand);\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/app/console/generate/GenerateApiDocsCommand.mts"],"sourcesContent":["/*\nhow this command should work:\n<command> generate-from-routes --output path/to/output.json\n<command> generate-base --output path/to/output.json\n<command> merge-files # file lists/details are in config\n<command> # will show help\n*/\nimport { cli, router } from \"../../../facades.mjs\";\nimport { Command, Option } from \"clipanion\";\nimport path from \"path\";\nimport * as fs from \"fs/promises\";\nimport { config } from \"../../../config.mjs\";\nimport { Arr } from \"@devbro/neko-helper\";\n\nexport class GenerateApiDocsCommand extends Command {\n static paths = [[`generate`, `apidocs`]];\n\n static usage = Command.Usage({\n category: `Generate`,\n description: `Generate OpenAPI documentation from routes`,\n details: `\n This command utility generates OpenAPI 3.0 specification documentation by analyzing\n your application's routes and merging with example files.\n \n Subcommands:\n - generate-from-routes: Generate OpenAPI spec from registered routes\n - generate-base: Generate base OpenAPI specification structure\n - merge-files: Merge multiple OpenAPI files into final documentation\n\n \n This command depends on config data. make sure your default config contains the following:\n\n\\`\\`\\`\napi_docs: {\n\n merge_files: [\n\n path.join(__dirname, '../..', 'private', 'openapi_base.json'),\n\n path.join(__dirname, '../..', 'private', 'openapi_from_routes.json'),\n\n path.join(__dirname, '../..', 'private', 'openapi_from_tests.json'),\n\n path.join(__dirname, '../..', 'private', 'openapi_other_user_changes.json'),\n\n ],\n\n output: path.join(__dirname, '../..', 'public', 'openapi.json'),\n\n}\n\n\\`\\`\\`\n `,\n examples: [\n [\n `Generate from routes`,\n `$0 generate apidocs generate-from-routes --output path/to/output.json`,\n ],\n [\n `Generate base spec`,\n `$0 generate apidocs generate-base --output path/to/output.json`,\n ],\n [`Merge files`, `$0 generate apidocs merge-files`],\n [`Show help`, `$0 generate apidocs --help`],\n ],\n });\n\n subcommand = Option.String({ required: false });\n\n output = Option.String(`--output,-o`, {\n description: `Output file path for generated documentation`,\n });\n\n config = Option.String(`--config,-c`, {\n description: `Path in config to get details from (default: api_docs)`,\n required: false,\n });\n\n async execute() {\n if (!this.subcommand) {\n this.context.stdout.write(\n this.constructor.usage?.toString() || \"No help available\\n\",\n );\n return 0;\n }\n\n switch (this.subcommand) {\n case \"generate-from-routes\":\n return await this.executeGenerateFromRoutes();\n case \"generate-base\":\n return await this.executeGenerateBase();\n case \"merge-files\":\n return await this.executeMergeFiles();\n default:\n this.context.stderr.write(`Unknown subcommand: ${this.subcommand}\\n`);\n this.context.stdout.write(\n this.constructor.usage?.toString() || \"No help available\\n\",\n );\n return 1;\n }\n }\n\n private async executeGenerateFromRoutes() {\n this.context.stdout.write(\n `Generating OpenAPI documentation from routes...\\n`,\n );\n\n const openApiSpec = this.generateFromRoutes();\n const outputPath =\n this.output ||\n path.join(config.get(\"private_path\"), \"openapi_from_routes.json\");\n\n // Ensure directory exists\n await fs.mkdir(path.dirname(outputPath), { recursive: true });\n\n await fs.writeFile(\n outputPath,\n JSON.stringify(openApiSpec, null, 2),\n \"utf-8\",\n );\n\n this.context.stdout.write(\n `OpenAPI routes documentation generated at: ${outputPath}\\n`,\n );\n this.context.stdout.write(\n `Total routes documented: ${Object.keys(openApiSpec.paths).length}\\n`,\n );\n return 0;\n }\n\n private async executeGenerateBase() {\n this.context.stdout.write(`Generating base OpenAPI specification...\\n`);\n\n const baseSpec = this.getBaseOpenApiSpec();\n const outputPath =\n this.output || path.join(config.get(\"private_path\"), \"openapi_base.json\");\n\n // Ensure directory exists\n await fs.mkdir(path.dirname(outputPath), { recursive: true });\n\n await fs.writeFile(outputPath, JSON.stringify(baseSpec, null, 2), \"utf-8\");\n\n this.context.stdout.write(\n `Base OpenAPI specification generated at: ${outputPath}\\n`,\n );\n return 0;\n }\n\n private async executeMergeFiles() {\n this.context.stdout.write(`Merging OpenAPI files...\\n`);\n let configPath = this.config || \"api_docs\";\n\n const files_to_merge: string[] = config.get(`${configPath}.merge_files`);\n let final_api_docs = {};\n\n for (const file_path of files_to_merge) {\n try {\n const file_json = JSON.parse(await fs.readFile(file_path, \"utf8\"));\n final_api_docs = Arr.deepMerge(final_api_docs, file_json);\n this.context.stdout.write(` Merged: ${file_path}\\n`);\n } catch (error) {\n this.context.stderr.write(\n ` Warning: Could not read ${file_path}: ${(error as Error).message}\\n`,\n );\n }\n }\n\n const outputPath = this.output || config.get(`${configPath}.output`);\n\n // Ensure directory exists\n await fs.mkdir(path.dirname(outputPath), { recursive: true });\n\n await fs.writeFile(outputPath, JSON.stringify(final_api_docs, null, 2));\n\n this.context.stdout.write(\n `Final OpenAPI document written to: ${outputPath}\\n`,\n );\n return 0;\n }\n\n private extractParameters(routePath: string): any[] {\n const paramRegex = /:([a-zA-Z0-9_]+)/g;\n const parameters: any[] = [];\n let match;\n\n while ((match = paramRegex.exec(routePath)) !== null) {\n parameters.push({\n name: match[1],\n in: \"path\",\n required: true,\n schema: {\n type: \"string\",\n },\n description: `Path parameter ${match[1]}`,\n });\n }\n\n return parameters;\n }\n\n private generateFromRoutes() {\n const openApiSpec = {\n paths: {} as any,\n };\n const routes = router().routes;\n\n // Process each route\n for (const route of routes) {\n const routePath = route.path;\n // Convert route path to OpenAPI format (e.g., /api/:id -> /api/{id})\n const openApiPath = routePath.replace(/\\/$/g, \"\"); //.replace(/:([a-zA-Z0-9_]+)/g, \"{$1}\");\n\n if (!openApiSpec.paths[openApiPath]) {\n openApiSpec.paths[openApiPath] = {};\n }\n\n // Add each HTTP method for this route\n for (const method of route.methods) {\n const lowerMethod = method.toLowerCase();\n\n // Skip HEAD as it's usually auto-generated\n if (lowerMethod === \"head\") {\n continue;\n }\n\n openApiSpec.paths[openApiPath][lowerMethod] = {\n summary: `${routePath}`,\n description: `Endpoint for ${method} ${routePath}`,\n security: [],\n parameters: this.extractParameters(routePath),\n responses: {},\n };\n\n // Add request body for POST, PUT, PATCH\n if ([\"post\", \"put\", \"patch\"].includes(lowerMethod)) {\n openApiSpec.paths[openApiPath][lowerMethod].requestBody = {\n required: true,\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n },\n },\n },\n };\n }\n }\n }\n\n return openApiSpec;\n }\n\n getBaseOpenApiSpec() {\n // Generate OpenAPI 3.0 specification\n const openApiSpec = {\n openapi: \"3.0.0\",\n info: {\n title: \"API Documentation\",\n version: \"1.0.0\",\n description: \"Auto-generated API documentation\",\n },\n servers: [\n {\n url: \"/\",\n description: \"Local server\",\n },\n ],\n components: {\n securitySchemes: {\n bearerAuth: {\n type: \"http\",\n scheme: \"bearer\",\n bearerFormat: \"JWT\",\n description: \"JWT token authentication\",\n },\n },\n },\n security: [\n {\n bearerAuth: [],\n },\n ],\n paths: {} as Record<string, any>,\n };\n\n return openApiSpec;\n }\n}\n\ncli().register(GenerateApiDocsCommand);\n"],"mappings":"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","names":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LocalStorageProviderConfig } from '@devbro/pashmak/storage';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
4
|
|
|
@@ -7,6 +7,6 @@ export default {
|
|
|
7
7
|
provider: 'local',
|
|
8
8
|
config: {
|
|
9
9
|
basePath: path.join(os.tmpdir(), '/app-storage/'),
|
|
10
|
-
} as
|
|
10
|
+
} as LocalStorageProviderConfig,
|
|
11
11
|
},
|
|
12
12
|
};
|
|
@@ -37,6 +37,9 @@ var Middleware = class {
|
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
+
// ../neko-router/dist/CompiledRoute.mjs
|
|
41
|
+
var import_neko_helper = require("@devbro/neko-helper");
|
|
42
|
+
|
|
40
43
|
// src/DatabaseServiceProvider.mts
|
|
41
44
|
var import_neko_sql = require("@devbro/neko-sql");
|
|
42
45
|
var import_neko_orm = require("@devbro/neko-orm");
|
|
@@ -128,8 +131,15 @@ var DatabaseServiceProvider = class _DatabaseServiceProvider extends Middleware
|
|
|
128
131
|
return _DatabaseServiceProvider.instance;
|
|
129
132
|
}
|
|
130
133
|
getConnection(db_config) {
|
|
131
|
-
|
|
132
|
-
|
|
134
|
+
if (db_config.provider === "postgresql") {
|
|
135
|
+
const conn = new import_neko_sql.PostgresqlConnection(db_config.config);
|
|
136
|
+
return conn;
|
|
137
|
+
}
|
|
138
|
+
if (db_config.provider === "sqlite") {
|
|
139
|
+
const conn = new import_neko_sql.SqliteConnection(db_config.config);
|
|
140
|
+
return conn;
|
|
141
|
+
}
|
|
142
|
+
throw new Error(`Unsupported database provider: ${db_config.provider}`);
|
|
133
143
|
}
|
|
134
144
|
};
|
|
135
145
|
// Annotate the CommonJS export names for ESM import in node:
|