@devbro/pashmak 0.1.47 → 0.1.49

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.
Files changed (42) hide show
  1. package/dist/DatabaseServiceProvider.d.mts +4 -1
  2. package/dist/DatabaseServiceProvider.mjs +5 -2
  3. package/dist/DatabaseServiceProvider.mjs.map +1 -1
  4. package/dist/app/console/StartCommand.d.mts +1 -0
  5. package/dist/app/console/StartCommand.mjs +5 -2
  6. package/dist/app/console/StartCommand.mjs.map +1 -1
  7. package/dist/app/console/generate/GenerateApiDocsCommand.d.mts +34 -2
  8. package/dist/app/console/generate/GenerateApiDocsCommand.mjs +183 -100
  9. package/dist/app/console/generate/GenerateApiDocsCommand.mjs.map +1 -1
  10. package/dist/app/console/project/base_project/src/config/storages.ts.tpl +2 -2
  11. package/dist/bin/DatabaseServiceProvider.cjs +8 -2
  12. package/dist/bin/app/console/DefaultCommand.cjs +64 -32
  13. package/dist/bin/app/console/KeyGenerateCommand.cjs +64 -32
  14. package/dist/bin/app/console/StartCommand.cjs +69 -34
  15. package/dist/bin/app/console/generate/GenerateApiDocsCommand.cjs +248 -133
  16. package/dist/bin/app/console/generate/GenerateControllerCommand.cjs +64 -32
  17. package/dist/bin/app/console/generate/index.cjs +248 -133
  18. package/dist/bin/app/console/index.cjs +253 -135
  19. package/dist/bin/app/console/migrate/GenerateMigrateCommand.cjs +64 -32
  20. package/dist/bin/app/console/migrate/MigrateCommand.cjs +64 -32
  21. package/dist/bin/app/console/migrate/MigrateRollbackCommand.cjs +64 -32
  22. package/dist/bin/app/console/migrate/index.cjs +64 -32
  23. package/dist/bin/app/console/queue/GenerateQueueMigrateCommand.cjs +64 -32
  24. package/dist/bin/cache.cjs +64 -32
  25. package/dist/bin/facades.cjs +64 -32
  26. package/dist/bin/factories.cjs +64 -32
  27. package/dist/bin/http.cjs +739 -0
  28. package/dist/bin/index.cjs +264 -141
  29. package/dist/bin/middlewares.cjs +66 -34
  30. package/dist/bin/queue.cjs +64 -32
  31. package/dist/bin/router.cjs +4 -8
  32. package/dist/config.d.mts +0 -1
  33. package/dist/facades.mjs +2 -13
  34. package/dist/facades.mjs.map +1 -1
  35. package/dist/factories.mjs +45 -2
  36. package/dist/factories.mjs.map +1 -1
  37. package/dist/http.d.mts +4 -0
  38. package/dist/http.mjs +20 -0
  39. package/dist/http.mjs.map +1 -1
  40. package/dist/queue.d.mts +1 -1
  41. package/dist/queue.mjs.map +1 -1
  42. package/package.json +1 -1
@@ -7,7 +7,10 @@ declare class DatabaseServiceProvider extends Middleware {
7
7
  private static instance;
8
8
  register(): Promise<void>;
9
9
  static getInstance(): DatabaseServiceProvider;
10
- getConnection(db_config: PoolConfig): Connection;
10
+ getConnection(db_config: {
11
+ provider: string;
12
+ config: PoolConfig;
13
+ }): Connection;
11
14
  }
12
15
 
13
16
  export { DatabaseServiceProvider };
@@ -49,8 +49,11 @@ class DatabaseServiceProvider extends Middleware {
49
49
  return DatabaseServiceProvider.instance;
50
50
  }
51
51
  getConnection(db_config) {
52
- const conn = new PostgresqlConnection(db_config);
53
- return conn;
52
+ if (db_config.provider === "postgresql") {
53
+ const conn = new PostgresqlConnection(db_config.config);
54
+ return conn;
55
+ }
56
+ throw new Error(`Unsupported database provider: ${db_config.provider}`);
54
57
  }
55
58
  }
56
59
  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, PoolConfig & { name: string }> =\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: PoolConfig): Connection {\n const conn = new PostgresqlConnection(db_config);\n return conn;\n }\n}\n"],"mappings":";;AAAA,SAAS,kBAAkB;AAE3B,SAAS,4BAA4B;AAGrC,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,WAAmC;AAC/C,UAAM,OAAO,IAAI,qBAAqB,SAAS;AAC/C,WAAO;AAAA,EACT;AACF;","names":[]}
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, { provider: string; config: PoolConfig }> =\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;\n }): Connection {\n if (db_config.provider === \"postgresql\") {\n const conn = new PostgresqlConnection(db_config.config);\n return conn;\n }\n\n throw new Error(`Unsupported database provider: ${db_config.provider}`);\n }\n}\n"],"mappings":";;AAAA,SAAS,kBAAkB;AAE3B,SAAS,4BAA4B;AAGrC,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,MAAM;AACtD,aAAO;AAAA,IACT;AAEA,UAAM,IAAI,MAAM,kCAAkC,UAAU,QAAQ,EAAE;AAAA,EACxE;AACF;","names":[]}
@@ -2,6 +2,7 @@ import { Command } from 'clipanion';
2
2
 
3
3
  declare class StartCommand extends Command {
4
4
  scheduler: boolean;
5
+ cron: boolean;
5
6
  http: boolean;
6
7
  queue: boolean;
7
8
  all: boolean;
@@ -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((x) => x).length == 0) {
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)\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.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,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,WAAW,KAAK,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,EAC9D,UAAU,GACb;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,KAAK;AAC9B,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":[]}
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
- help: boolean;
8
- execute(): Promise<0 | undefined>;
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`, `apidocsv2`]];
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
- The generated documentation includes:
25
- - All registered routes with their HTTP methods
26
- - Path parameters extracted from route definitions
27
- - Request body schemas for POST, PUT, and PATCH methods
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
- api_docs: {
35
- merge_files: [
36
- path.join(__dirname, '../..', 'private', 'openapi_examples.json'),
37
- path.join(__dirname, '../..', 'private', 'openapi_base.json'),
38
- path.join(__dirname, '../..', 'private', 'openapi_user_changes.json'),
39
- ],
40
- output: path.join(__dirname, '../..', 'private', 'openapi.json'),
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: [[`Generate API documentation`, `$0 generate apidocs`]]
50
+ examples: [
51
+ [
52
+ `Generate from routes`,
53
+ `$0 generate apidocsv2 generate-from-routes --output path/to/output.json`
54
+ ],
55
+ [
56
+ `Generate base spec`,
57
+ `$0 generate apidocsv2 generate-base --output path/to/output.json`
58
+ ],
59
+ [`Merge files`, `$0 generate apidocsv2 merge-files`],
60
+ [`Show help`, `$0 generate apidocsv2 --help`]
61
+ ]
44
62
  });
45
- help = Option.Boolean(`--help,-h`, false, {
46
- description: `Show help message for this command`
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.help) {
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
- const rootDir = process.cwd();
56
- this.context.stdout.write(`Generating OpenAPI documentation...
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
- const routes = router().routes;
59
- const openApiSpec = {
60
- openapi: "3.0.0",
61
- info: {
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
- await fs.mkdir(config.get("private_path"), { recursive: true });
120
- const outputPath = path.join(config.get("private_path"), "openapi.json");
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(`Total routes documented: ${routes.length}
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
- let files_to_merge = config.get("api_docs.merge_files");
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 (let file_path of files_to_merge) {
135
- let file_json = JSON.parse(await fs.readFile(file_path, "utf8"));
136
- final_api_docs = Arr.deepMerge(final_api_docs, file_json);
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
- await fs.writeFile(
139
- config.get("api_docs.output"),
140
- JSON.stringify(final_api_docs, null, 2)
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
- `wrote final open api document to : ${config.get("api_docs.output")}
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`, `apidocsv2`]];\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 apidocsv2 generate-from-routes --output path/to/output.json`,\n ],\n [\n `Generate base spec`,\n `$0 generate apidocsv2 generate-base --output path/to/output.json`,\n ],\n [`Merge files`, `$0 generate apidocsv2 merge-files`],\n [`Show help`, `$0 generate apidocsv2 --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 { LocalStorageConfig } from '@devbro/pashmak/storage';
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 LocalStorageConfig,
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,11 @@ var DatabaseServiceProvider = class _DatabaseServiceProvider extends Middleware
128
131
  return _DatabaseServiceProvider.instance;
129
132
  }
130
133
  getConnection(db_config) {
131
- const conn = new import_neko_sql.PostgresqlConnection(db_config);
132
- return conn;
134
+ if (db_config.provider === "postgresql") {
135
+ const conn = new import_neko_sql.PostgresqlConnection(db_config.config);
136
+ return conn;
137
+ }
138
+ throw new Error(`Unsupported database provider: ${db_config.provider}`);
133
139
  }
134
140
  };
135
141
  // Annotate the CommonJS export names for ESM import in node: