@kanjijs/cli 0.2.0-beta.15 → 0.2.0-beta.16

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.
@@ -8,4 +8,5 @@ export declare class ModuleCompiler {
8
8
  private processProviders;
9
9
  private validate;
10
10
  private checkDependencies;
11
+ private registerProviders;
11
12
  }
package/dist/index.js CHANGED
@@ -14418,7 +14418,8 @@ class Container {
14418
14418
  resolve(token) {
14419
14419
  const provider = this.providers.get(token);
14420
14420
  if (!provider) {
14421
- throw new Error(`[DI] Provider not found for token: ${typeof token === "function" ? token.name : String(token)}`);
14421
+ const tokenName = typeof token === "function" ? token.name ?? "anonymous" : String(token);
14422
+ throw new Error(`[DI] Provider not found for token: ${tokenName}`);
14422
14423
  }
14423
14424
  if (provider.instance) {
14424
14425
  return provider.instance;
@@ -14468,12 +14469,14 @@ var init_container = __esm(() => {
14468
14469
  KanjijsIoC.providers.set(target, provider);
14469
14470
  }
14470
14471
  if (!provider) {
14471
- throw new Error(`Provider not found for token: ${typeof target === "function" ? target.name : String(target)}`);
14472
+ const targetName2 = typeof target === "function" ? target.name ?? "anonymous" : String(target);
14473
+ throw new Error(`Provider not found for token: ${targetName2}`);
14472
14474
  }
14473
14475
  if (provider.instance) {
14474
14476
  return provider.instance;
14475
14477
  }
14476
- console.log(`[DI] Creating NEW instance for ${typeof target === "function" ? target.name : String(target)}`);
14478
+ const targetName = typeof target === "function" ? target.name ?? "anonymous" : String(target);
14479
+ console.log(`[DI] Creating NEW instance for ${targetName}`);
14477
14480
  if (provider.useValue !== undefined) {
14478
14481
  provider.instance = provider.useValue;
14479
14482
  } else if (provider.useClass) {
@@ -14503,12 +14506,11 @@ class ModuleCompiler {
14503
14506
  this.scan(rootModule);
14504
14507
  this.validate();
14505
14508
  const container = new Container;
14506
- for (const node of this.nodes.values()) {
14507
- for (const [token, provider] of node.providers) {
14508
- const { provide: _provide, ...definition } = provider;
14509
- container.register(token, definition);
14510
- }
14509
+ const rootNode = this.nodes.get(rootModule);
14510
+ if (!rootNode) {
14511
+ return container;
14511
14512
  }
14513
+ this.registerProviders(rootNode, container, new Set);
14512
14514
  return container;
14513
14515
  }
14514
14516
  scan(target) {
@@ -14595,16 +14597,28 @@ class ModuleCompiler {
14595
14597
  return overrideToken || paramType;
14596
14598
  });
14597
14599
  } else if ("useFactory" in provider) {
14598
- targetName = typeof provider.provide === "function" ? provider.provide.name : String(provider.provide);
14600
+ targetName = typeof provider.provide === "function" ? provider.provide.name ?? "anonymous" : String(provider.provide);
14599
14601
  dependencies = provider.inject || [];
14600
14602
  }
14601
14603
  for (const dep of dependencies) {
14602
14604
  if (!visibleTokens.has(dep)) {
14603
- const depName = typeof dep === "function" ? dep.name : String(dep);
14605
+ const depName = typeof dep === "function" ? dep.name ?? "anonymous" : String(dep);
14604
14606
  throw new Error(`[Kanjijs] strict-di-error: Provider '${targetName}' in Module '${moduleName}' ` + `depends on '${depName}', but it is not visible. ` + `Make sure it is imported and exported by the source module.`);
14605
14607
  }
14606
14608
  }
14607
14609
  }
14610
+ registerProviders(node, container, visited) {
14611
+ if (visited.has(node))
14612
+ return;
14613
+ visited.add(node);
14614
+ for (const imp of node.imports) {
14615
+ this.registerProviders(imp, container, visited);
14616
+ }
14617
+ for (const [token, provider] of node.providers) {
14618
+ const { provide: _provide, ...definition } = provider;
14619
+ container.register(token, definition);
14620
+ }
14621
+ }
14608
14622
  }
14609
14623
  var init_module_compiler = __esm(() => {
14610
14624
  init_metadata();
@@ -19990,7 +20004,7 @@ var import_picocolors2 = __toESM(require_picocolors(), 1);
19990
20004
  import path2 from "path";
19991
20005
  class GenerateCommand extends Command2 {
19992
20006
  load(program2) {
19993
- program2.command("g <type> <name>").description("Generate a new resource (e.g. 'kanjijs g resource users')").alias("generate").action(async (type, name) => {
20007
+ program2.command("g <type> <name>").description("Generate a new resource (e.g. 'kanjijs g resource users')").alias("generate").option("-r, --repository", "Generate with Repository pattern").action(async (type, name, options) => {
19994
20008
  if (type !== "resource") {
19995
20009
  console.error(import_picocolors2.default.red(`Unknown type: ${type}. Only 'resource' is supported.`));
19996
20010
  process.exit(1);
@@ -19998,22 +20012,133 @@ class GenerateCommand extends Command2 {
19998
20012
  const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
19999
20013
  const className = capitalize(name);
20000
20014
  const lowerName = name.toLowerCase();
20001
- const targetDir = path2.join(process.cwd(), "src", "modules", lowerName);
20015
+ const modulesDir = path2.join(process.cwd(), "src", "modules");
20016
+ const targetDir = path2.join(modulesDir, lowerName);
20002
20017
  await import_fs_extra.default.ensureDir(targetDir);
20003
- const serviceTpl = `import { Injectable } from "@kanjijs/core";
20018
+ try {
20019
+ let repositoryClass = "";
20020
+ let serviceImports = "";
20021
+ let serviceConstructor = `constructor(private readonly logger: Logger) {}`;
20022
+ let serviceMethods = ` findAll() {
20023
+ this.logger.info("Finding all ${lowerName}");
20024
+ return "This action returns all ${lowerName}";
20025
+ }`;
20026
+ if (options.repository) {
20027
+ const commonDir = path2.join(process.cwd(), "src", "common");
20028
+ const baseRepoPath = path2.join(commonDir, "base.repository.ts");
20029
+ if (!await import_fs_extra.default.pathExists(baseRepoPath)) {
20030
+ await import_fs_extra.default.ensureDir(commonDir);
20031
+ const baseRepoTpl = `import { type DrizzleDb } from "@kanjijs/store";
20032
+ import { eq } from "drizzle-orm";
20033
+ import { type PgTableWithColumns } from "drizzle-orm/pg-core";
20034
+
20035
+ /**
20036
+ * Base Repository for generic CRUD operations.
20037
+ * Table-First: Infers types from Drizzle schema.
20038
+ */
20039
+ export abstract class BaseRepository<
20040
+ TTable extends PgTableWithColumns<any>,
20041
+ TEntity = TTable["$inferSelect"],
20042
+ TCreate = TTable["$inferInsert"],
20043
+ TUpdate = Partial<TTable["$inferInsert"]>
20044
+ > {
20045
+ constructor(
20046
+ protected readonly db: DrizzleDb,
20047
+ protected readonly table: TTable,
20048
+ protected readonly idColumn: any
20049
+ ) {}
20050
+
20051
+ async findAll(): Promise<TEntity[]> {
20052
+ return this.db.select().from(this.table) as unknown as Promise<TEntity[]>;
20053
+ }
20054
+
20055
+ async findById(id: number | string): Promise<TEntity | undefined> {
20056
+ const result = await this.db
20057
+ .select()
20058
+ .from(this.table)
20059
+ .where(eq(this.idColumn, id));
20060
+
20061
+ // @ts-ignore
20062
+ return result[0];
20063
+ }
20064
+
20065
+ async create(data: TCreate): Promise<TEntity> {
20066
+ // @ts-ignore
20067
+ const result = await this.db.insert(this.table).values(data).returning();
20068
+ // @ts-ignore
20069
+ return result[0];
20070
+ }
20071
+
20072
+ async update(id: number | string, data: TUpdate): Promise<TEntity | undefined> {
20073
+ const result = await this.db
20074
+ .update(this.table)
20075
+ // @ts-ignore
20076
+ .set(data)
20077
+ .where(eq(this.idColumn, id))
20078
+ .returning();
20079
+
20080
+ // @ts-ignore
20081
+ return result[0];
20082
+ }
20083
+
20084
+ async delete(id: number | string): Promise<TEntity | undefined> {
20085
+ const result = await this.db
20086
+ .delete(this.table)
20087
+ .where(eq(this.idColumn, id))
20088
+ .returning();
20089
+
20090
+ // @ts-ignore
20091
+ return result[0];
20092
+ }
20093
+ }
20094
+ `;
20095
+ await import_fs_extra.default.writeFile(baseRepoPath, baseRepoTpl);
20096
+ console.log(import_picocolors2.default.green(`CREATED src/common/base.repository.ts (First time setup)`));
20097
+ }
20098
+ repositoryClass = `${className}Repository`;
20099
+ const repoPath = path2.join(targetDir, `${lowerName}.repository.ts`);
20100
+ const repoTpl = `import { Injectable, Inject } from "@kanjijs/core";
20101
+ import { DATABASE_CLIENT, type DrizzleDb } from "@kanjijs/store";
20102
+ import { BaseRepository } from "../../common/base.repository";
20103
+ // import { ${lowerName} } from "../../db/schema"; // TODO: Import your Drizzle table
20104
+
20105
+ @Injectable()
20106
+ export class ${repositoryClass} extends BaseRepository<any> { // TODO: Replace 'any' with 'typeof ${lowerName}'
20107
+ constructor(@Inject(DATABASE_CLIENT) db: DrizzleDb) {
20108
+ super(db, {} as any, "id"); // TODO: Pass real table and ID column
20109
+ }
20110
+ }
20111
+ `;
20112
+ await import_fs_extra.default.writeFile(repoPath, repoTpl);
20113
+ console.log(import_picocolors2.default.white(`CREATE ${lowerName}.repository.ts`));
20114
+ serviceImports = `import { ${repositoryClass} } from "./${lowerName}.repository";`;
20115
+ serviceConstructor = `constructor(
20116
+ private readonly logger: Logger,
20117
+ private readonly repo: ${repositoryClass}
20118
+ ) {}`;
20119
+ serviceMethods = ` async findAll() {
20120
+ this.logger.info("Finding all ${lowerName}");
20121
+ return this.repo.findAll();
20122
+ }
20123
+
20124
+ async create(data: any) {
20125
+ return this.repo.create(data);
20126
+ }`;
20127
+ }
20128
+ const serviceTpl = `import { Injectable } from "@kanjijs/core";
20004
20129
  import { Logger } from "@kanjijs/logger";
20130
+ ${serviceImports}
20005
20131
 
20006
20132
  @Injectable()
20007
20133
  export class ${className}Service {
20008
- constructor(private readonly logger: Logger) {}
20134
+ ${serviceConstructor}
20009
20135
 
20010
- findAll() {
20011
- this.logger.info("Finding all ${lowerName}");
20012
- return "This action returns all ${lowerName}";
20013
- }
20136
+ ${serviceMethods}
20014
20137
  }
20015
20138
  `;
20016
- const controllerTpl = `import { Controller, Get, Post, Body } from "@kanjijs/core";
20139
+ await import_fs_extra.default.writeFile(path2.join(targetDir, `${lowerName}.service.ts`), serviceTpl);
20140
+ console.log(import_picocolors2.default.white(`CREATE ${lowerName}.service.ts`));
20141
+ const controllerTpl = `import { Controller, Get, Post, Body } from "@kanjijs/core";
20017
20142
  import { type Context } from "hono";
20018
20143
  import { ${className}Service } from "./${lowerName}.service";
20019
20144
  import { Contract } from "@kanjijs/contracts";
@@ -20035,46 +20160,49 @@ export class ${className}Controller {
20035
20160
  }
20036
20161
 
20037
20162
  @Post("/", { contract: Create${className}Contract })
20038
- create(@Body() body: typeof Create${className}Contract.body) {
20039
- return { success: true, data: body };
20163
+ create(c: Context) {
20164
+ // Infer keys from contract (Contract-first)
20165
+ const body = c.get("kanji.validated.body");
20166
+ return c.json(this.service.create(body));
20040
20167
  }
20041
20168
  }
20042
20169
  `;
20043
- const moduleTpl = `import { Module } from "@kanjijs/core";
20044
- import { ${className}Controller } from "./${lowerName}.controller";
20045
- import { ${className}Service } from "./${lowerName}.service";
20170
+ await import_fs_extra.default.writeFile(path2.join(targetDir, `${lowerName}.controller.ts`), controllerTpl);
20171
+ console.log(import_picocolors2.default.white(`CREATE ${lowerName}.controller.ts`));
20172
+ let moduleProviders = `[${className}Service]`;
20173
+ let moduleImports = `import { ${className}Controller } from "./${lowerName}.controller";
20174
+ import { ${className}Service } from "./${lowerName}.service";`;
20175
+ if (options.repository) {
20176
+ moduleProviders = `[${className}Service, ${repositoryClass}]`;
20177
+ moduleImports += `
20178
+ import { ${repositoryClass} } from "./${lowerName}.repository";`;
20179
+ }
20180
+ const moduleTpl = `import { Module } from "@kanjijs/core";
20046
20181
  import { LoggerModule } from "@kanjijs/logger";
20182
+ ${moduleImports}
20047
20183
 
20048
20184
  @Module({
20049
20185
  imports: [LoggerModule],
20050
20186
  controllers: [${className}Controller],
20051
- providers: [${className}Service]
20187
+ providers: ${moduleProviders}
20052
20188
  })
20053
20189
  export class ${className}Module {}
20054
20190
  `;
20055
- try {
20056
- await import_fs_extra.default.writeFile(path2.join(targetDir, `${lowerName}.service.ts`), serviceTpl);
20057
- await import_fs_extra.default.writeFile(path2.join(targetDir, `${lowerName}.controller.ts`), controllerTpl);
20058
20191
  await import_fs_extra.default.writeFile(path2.join(targetDir, `${lowerName}.module.ts`), moduleTpl);
20059
- console.log(import_picocolors2.default.green(`
20060
- Generated resource ${lowerName} in src/modules/${lowerName}`));
20061
- console.log(import_picocolors2.default.white(`CREATE ${lowerName}.service.ts`));
20062
- console.log(import_picocolors2.default.white(`CREATE ${lowerName}.controller.ts`));
20063
20192
  console.log(import_picocolors2.default.white(`CREATE ${lowerName}.module.ts`));
20064
20193
  const appModulePath = path2.join(process.cwd(), "src", "app.module.ts");
20065
20194
  if (await import_fs_extra.default.pathExists(appModulePath)) {
20066
20195
  let content = await import_fs_extra.default.readFile(appModulePath, "utf-8");
20067
20196
  const moduleName = `${className}Module`;
20068
- const alreadyIncluded = content.includes(moduleName);
20069
- const importStatement = `import { ${className}Module } from "./modules/${lowerName}/${lowerName}.module";`;
20070
- if (!content.includes(importStatement)) {
20071
- const lastImportIndex = content.lastIndexOf("import ");
20072
- const endOfLastImport = content.indexOf(`
20197
+ if (!content.includes(moduleName)) {
20198
+ const importStatement = `import { ${className}Module } from "./modules/${lowerName}/${lowerName}.module";`;
20199
+ if (!content.includes(importStatement)) {
20200
+ const lastImportIndex = content.lastIndexOf("import ");
20201
+ const endOfLastImport = content.indexOf(`
20073
20202
  `, lastImportIndex) + 1;
20074
- content = content.slice(0, endOfLastImport) + importStatement + `
20075
- ` + content.slice(endOfLastImport);
20076
- }
20077
- if (!alreadyIncluded) {
20203
+ content = `${content.slice(0, endOfLastImport)}${importStatement}
20204
+ ${content.slice(endOfLastImport)}`;
20205
+ }
20078
20206
  if (content.includes("imports: [")) {
20079
20207
  content = content.replace("imports: [", `imports: [${moduleName}, `);
20080
20208
  } else {
@@ -20086,9 +20214,9 @@ Generated resource ${lowerName} in src/modules/${lowerName}`));
20086
20214
  } else {
20087
20215
  console.log(import_picocolors2.default.dim(`WARN: ${moduleName} already in AppModule`));
20088
20216
  }
20089
- } else {
20090
- console.error(import_picocolors2.default.yellow(`WARN: AppModule not found at ${appModulePath}`));
20091
20217
  }
20218
+ console.log(import_picocolors2.default.green(`
20219
+ Success! Generated resource ${lowerName}.`));
20092
20220
  } catch (err) {
20093
20221
  console.error(import_picocolors2.default.red("Error generating resource:"), err);
20094
20222
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kanjijs/cli",
3
- "version": "0.2.0-beta.15",
3
+ "version": "0.2.0-beta.16",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "kanji": "./dist/index.js"
@@ -21,7 +21,7 @@
21
21
  "fs-extra": "^11.3.3",
22
22
  "picocolors": "^1.1.1",
23
23
  "openapi-typescript-codegen": "^0.25.0",
24
- "@kanjijs/openapi": "^0.2.0-beta.15"
24
+ "@kanjijs/openapi": "^0.2.0-beta.16"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/fs-extra": "^11.0.4"