@devbro/pashmak 0.1.10 → 0.1.11

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.
@@ -3,6 +3,7 @@ import { Command } from 'clipanion';
3
3
  declare class MigrateCommand extends Command {
4
4
  static paths: string[][];
5
5
  fresh: boolean;
6
+ refresh: boolean;
6
7
  execute(): Promise<void>;
7
8
  }
8
9
 
@@ -11,29 +11,46 @@ class MigrateCommand extends Command {
11
11
  __name(this, "MigrateCommand");
12
12
  }
13
13
  static paths = [[`migrate`]];
14
- fresh = Option.Boolean("--fresh", false);
14
+ fresh = Option.Boolean(`--fresh`, false, {
15
+ description: `whether to delete and recreate database`
16
+ });
17
+ refresh = Option.Boolean(`--refresh`, false, {
18
+ description: `whether to drop all tables before running migrations by using rollback function`
19
+ });
15
20
  async execute() {
16
21
  await context_provider.run(async () => {
17
22
  const db = database();
18
23
  const schema = db.getSchema();
19
24
  if (this.fresh) {
20
- logger().info("dropping all tables!!");
21
- let retry = true;
22
- let retry_count = 0;
23
- while (retry && retry_count < 10) {
24
- retry = false;
25
- retry_count++;
26
- const tables = await schema.tables();
27
- for (const table of tables) {
28
- logger().info(`dropping table ${table.name}`);
29
- try {
30
- await schema.dropTable(table.name);
31
- } catch {
32
- logger().info(`failed to drop ${table.name}`);
33
- retry = true;
34
- }
25
+ throw new Error("not implemented");
26
+ }
27
+ if (this.refresh) {
28
+ logger().info("reverting all migrations!!");
29
+ const existing_migrations = await db.runQuery({
30
+ sql: "select * from migrations order by created_at DESC",
31
+ bindings: []
32
+ });
33
+ const migrationsDir2 = config.get("migration.path");
34
+ for (const migration_record of existing_migrations) {
35
+ logger().info(`rolling back ${migration_record.filename}`);
36
+ try {
37
+ const MigrationClass = (await import(path.join(migrationsDir2, migration_record.filename))).default;
38
+ const migrationInstance = new MigrationClass();
39
+ await migrationInstance.down(db.getSchema());
40
+ await db.runQuery({
41
+ sql: "delete from migrations where filename = $1",
42
+ bindings: [migration_record.filename]
43
+ });
44
+ } catch (error) {
45
+ logger().error(
46
+ `Failed to rollback migration ${migration_record.filename}: ${error}`
47
+ );
48
+ throw error;
35
49
  }
36
50
  }
51
+ logger().info(
52
+ `rolled back ${existing_migrations.length} migrations successfully!`
53
+ );
37
54
  }
38
55
  if (!await schema.tableExists("migrations")) {
39
56
  await schema.createTable("migrations", (blueprint) => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/app/console/migrate/MigrateCommand.mts"],"sourcesContent":["import { cli, db as database, logger } from \"../../../facades.mjs\";\nimport { Command, Option } from \"clipanion\";\nimport { Blueprint } from \"@devbro/neko-sql\";\nimport { context_provider } from \"@devbro/neko-context\";\nimport path from \"path\";\nimport fs from \"fs/promises\";\nimport { config } from \"@devbro/neko-config\";\nimport { Migration } from \"@devbro/neko-sql\";\n\nexport class MigrateCommand extends Command {\n static paths = [[`migrate`]];\n\n fresh = Option.Boolean(\"--fresh\", false);\n\n async execute() {\n await context_provider.run(async () => {\n // this.context.stdout.write(`Hello Migrate Command!\\n`);\n const db = database();\n const schema = db.getSchema();\n\n if (this.fresh) {\n logger().info(\"dropping all tables!!\");\n let retry = true;\n let retry_count = 0;\n while (retry && retry_count < 10) {\n retry = false;\n retry_count++;\n const tables = await schema.tables();\n for (const table of tables) {\n logger().info(`dropping table ${table.name}`);\n try {\n await schema.dropTable(table.name);\n } catch {\n logger().info(`failed to drop ${table.name}`);\n retry = true;\n }\n }\n }\n }\n\n //create migration table if not exists\n if (!(await schema.tableExists(\"migrations\"))) {\n await schema.createTable(\"migrations\", (blueprint: Blueprint) => {\n blueprint.id();\n blueprint.timestamps();\n blueprint.string(\"filename\");\n blueprint.integer(\"batch\");\n });\n }\n\n const migrationsDir = config.get(\"migration.path\");\n let files: string[] = [];\n\n const dirEntries = await fs.readdir(migrationsDir);\n files = dirEntries\n .filter((entry) => entry.endsWith(\".ts\") || entry.endsWith(\".js\"))\n .sort();\n let batch_number = await db.runQuery({\n sql: \"select max(batch) as next_batch from migrations\",\n bindings: [],\n });\n batch_number = batch_number[0].next_batch || 0;\n batch_number++;\n\n const migrations = await db.runQuery({\n sql: \"select * from migrations order by created_at ASC\",\n bindings: [],\n });\n\n const completed_migrations = migrations.map((r: any) => r.filename);\n const pending_migrations = files.filter(\n (file) => !completed_migrations.includes(file),\n );\n\n let migrated_count = 0;\n for (const class_to_migrate of pending_migrations) {\n logger().info(`migrating up ${class_to_migrate}`);\n const ClassToMigrate = (\n await import(path.join(migrationsDir, class_to_migrate))\n ).default;\n const c: Migration = new ClassToMigrate();\n await c.up(db.getSchema());\n await db.runQuery({\n sql: \"insert into migrations (filename, batch) values ($1,$2)\",\n bindings: [class_to_migrate, batch_number],\n });\n migrated_count++;\n }\n\n if (migrated_count === 0) {\n logger().warn(\"no migrations to run!\");\n return;\n }\n\n logger().info(`migrated ${migrated_count} migrations successfully!`);\n });\n }\n}\n\ncli().register(MigrateCommand);\n"],"mappings":";;AAAA,SAAS,KAAK,MAAM,UAAU,cAAc;AAC5C,SAAS,SAAS,cAAc;AAEhC,SAAS,wBAAwB;AACjC,OAAO,UAAU;AACjB,OAAO,QAAQ;AACf,SAAS,cAAc;AAGhB,MAAM,uBAAuB,QAAQ;AAAA,EAT5C,OAS4C;AAAA;AAAA;AAAA,EAC1C,OAAO,QAAQ,CAAC,CAAC,SAAS,CAAC;AAAA,EAE3B,QAAQ,OAAO,QAAQ,WAAW,KAAK;AAAA,EAEvC,MAAM,UAAU;AACd,UAAM,iBAAiB,IAAI,YAAY;AAErC,YAAM,KAAK,SAAS;AACpB,YAAM,SAAS,GAAG,UAAU;AAE5B,UAAI,KAAK,OAAO;AACd,eAAO,EAAE,KAAK,uBAAuB;AACrC,YAAI,QAAQ;AACZ,YAAI,cAAc;AAClB,eAAO,SAAS,cAAc,IAAI;AAChC,kBAAQ;AACR;AACA,gBAAM,SAAS,MAAM,OAAO,OAAO;AACnC,qBAAW,SAAS,QAAQ;AAC1B,mBAAO,EAAE,KAAK,kBAAkB,MAAM,IAAI,EAAE;AAC5C,gBAAI;AACF,oBAAM,OAAO,UAAU,MAAM,IAAI;AAAA,YACnC,QAAQ;AACN,qBAAO,EAAE,KAAK,kBAAkB,MAAM,IAAI,EAAE;AAC5C,sBAAQ;AAAA,YACV;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAGA,UAAI,CAAE,MAAM,OAAO,YAAY,YAAY,GAAI;AAC7C,cAAM,OAAO,YAAY,cAAc,CAAC,cAAyB;AAC/D,oBAAU,GAAG;AACb,oBAAU,WAAW;AACrB,oBAAU,OAAO,UAAU;AAC3B,oBAAU,QAAQ,OAAO;AAAA,QAC3B,CAAC;AAAA,MACH;AAEA,YAAM,gBAAgB,OAAO,IAAI,gBAAgB;AACjD,UAAI,QAAkB,CAAC;AAEvB,YAAM,aAAa,MAAM,GAAG,QAAQ,aAAa;AACjD,cAAQ,WACL,OAAO,CAAC,UAAU,MAAM,SAAS,KAAK,KAAK,MAAM,SAAS,KAAK,CAAC,EAChE,KAAK;AACR,UAAI,eAAe,MAAM,GAAG,SAAS;AAAA,QACnC,KAAK;AAAA,QACL,UAAU,CAAC;AAAA,MACb,CAAC;AACD,qBAAe,aAAa,CAAC,EAAE,cAAc;AAC7C;AAEA,YAAM,aAAa,MAAM,GAAG,SAAS;AAAA,QACnC,KAAK;AAAA,QACL,UAAU,CAAC;AAAA,MACb,CAAC;AAED,YAAM,uBAAuB,WAAW,IAAI,CAAC,MAAW,EAAE,QAAQ;AAClE,YAAM,qBAAqB,MAAM;AAAA,QAC/B,CAAC,SAAS,CAAC,qBAAqB,SAAS,IAAI;AAAA,MAC/C;AAEA,UAAI,iBAAiB;AACrB,iBAAW,oBAAoB,oBAAoB;AACjD,eAAO,EAAE,KAAK,gBAAgB,gBAAgB,EAAE;AAChD,cAAM,kBACJ,MAAM,OAAO,KAAK,KAAK,eAAe,gBAAgB,IACtD;AACF,cAAM,IAAe,IAAI,eAAe;AACxC,cAAM,EAAE,GAAG,GAAG,UAAU,CAAC;AACzB,cAAM,GAAG,SAAS;AAAA,UAChB,KAAK;AAAA,UACL,UAAU,CAAC,kBAAkB,YAAY;AAAA,QAC3C,CAAC;AACD;AAAA,MACF;AAEA,UAAI,mBAAmB,GAAG;AACxB,eAAO,EAAE,KAAK,uBAAuB;AACrC;AAAA,MACF;AAEA,aAAO,EAAE,KAAK,YAAY,cAAc,2BAA2B;AAAA,IACrE,CAAC;AAAA,EACH;AACF;AAEA,IAAI,EAAE,SAAS,cAAc;","names":[]}
1
+ {"version":3,"sources":["../../../../src/app/console/migrate/MigrateCommand.mts"],"sourcesContent":["import { cli, db as database, logger } from \"../../../facades.mjs\";\nimport { Command, Option } from \"clipanion\";\nimport { Blueprint } from \"@devbro/neko-sql\";\nimport { context_provider } from \"@devbro/neko-context\";\nimport path from \"path\";\nimport fs from \"fs/promises\";\nimport { config } from \"@devbro/neko-config\";\nimport { Migration } from \"@devbro/neko-sql\";\n\nexport class MigrateCommand extends Command {\n static paths = [[`migrate`]];\n\n fresh = Option.Boolean(`--fresh`, false, {\n description: `whether to delete and recreate database`,\n });\n\n refresh = Option.Boolean(`--refresh`, false, {\n description: `whether to drop all tables before running migrations by using rollback function`,\n });\n\n async execute() {\n await context_provider.run(async () => {\n // this.context.stdout.write(`Hello Migrate Command!\\n`);\n const db = database();\n const schema = db.getSchema();\n\n if (this.fresh) {\n throw new Error(\"not implemented\");\n }\n\n if (this.refresh) {\n logger().info(\"reverting all migrations!!\");\n // read all migrations and undo them all\n const existing_migrations = await db.runQuery({\n sql: \"select * from migrations order by created_at DESC\",\n bindings: [],\n });\n\n const migrationsDir = config.get(\"migration.path\");\n\n for (const migration_record of existing_migrations) {\n logger().info(`rolling back ${migration_record.filename}`);\n try {\n const MigrationClass = (\n await import(path.join(migrationsDir, migration_record.filename))\n ).default;\n const migrationInstance: Migration = new MigrationClass();\n\n // Call the down method to rollback the migration\n await migrationInstance.down(db.getSchema());\n\n // Remove the migration record from the migrations table\n await db.runQuery({\n sql: \"delete from migrations where filename = $1\",\n bindings: [migration_record.filename],\n });\n } catch (error) {\n logger().error(\n `Failed to rollback migration ${migration_record.filename}: ${error}`,\n );\n throw error;\n }\n }\n\n logger().info(\n `rolled back ${existing_migrations.length} migrations successfully!`,\n );\n }\n\n //create migration table if not exists\n if (!(await schema.tableExists(\"migrations\"))) {\n await schema.createTable(\"migrations\", (blueprint: Blueprint) => {\n blueprint.id();\n blueprint.timestamps();\n blueprint.string(\"filename\");\n blueprint.integer(\"batch\");\n });\n }\n\n const migrationsDir = config.get(\"migration.path\");\n let files: string[] = [];\n\n const dirEntries = await fs.readdir(migrationsDir);\n files = dirEntries\n .filter((entry) => entry.endsWith(\".ts\") || entry.endsWith(\".js\"))\n .sort();\n let batch_number = await db.runQuery({\n sql: \"select max(batch) as next_batch from migrations\",\n bindings: [],\n });\n batch_number = batch_number[0].next_batch || 0;\n batch_number++;\n\n const migrations = await db.runQuery({\n sql: \"select * from migrations order by created_at ASC\",\n bindings: [],\n });\n\n const completed_migrations = migrations.map((r: any) => r.filename);\n const pending_migrations = files.filter(\n (file) => !completed_migrations.includes(file),\n );\n\n let migrated_count = 0;\n for (const class_to_migrate of pending_migrations) {\n logger().info(`migrating up ${class_to_migrate}`);\n const ClassToMigrate = (\n await import(path.join(migrationsDir, class_to_migrate))\n ).default;\n const c: Migration = new ClassToMigrate();\n await c.up(db.getSchema());\n await db.runQuery({\n sql: \"insert into migrations (filename, batch) values ($1,$2)\",\n bindings: [class_to_migrate, batch_number],\n });\n migrated_count++;\n }\n\n if (migrated_count === 0) {\n logger().warn(\"no migrations to run!\");\n return;\n }\n\n logger().info(`migrated ${migrated_count} migrations successfully!`);\n });\n }\n}\n\ncli().register(MigrateCommand);\n"],"mappings":"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","names":["migrationsDir"]}
@@ -2,7 +2,7 @@ import { Command } from 'clipanion';
2
2
 
3
3
  declare class MigrateRollbackCommand extends Command {
4
4
  static paths: string[][];
5
- steps: number | undefined;
5
+ steps: number;
6
6
  execute(): Promise<void>;
7
7
  }
8
8
 
@@ -1,6 +1,6 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
- import { cli, db as database } from "../../../facades.mjs";
3
+ import { cli, db as database, logger } from "../../../facades.mjs";
4
4
  import { Command, Option } from "clipanion";
5
5
  import { context_provider } from "@devbro/neko-context";
6
6
  import path from "path";
@@ -12,7 +12,7 @@ class MigrateRollbackCommand extends Command {
12
12
  __name(this, "MigrateRollbackCommand");
13
13
  }
14
14
  static paths = [[`migrate`, "rollback"]];
15
- steps = Option.String(`--steps`, {
15
+ steps = Option.String(`--steps`, "1", {
16
16
  description: `how many migrations to rollback`,
17
17
  validator: t.isNumber()
18
18
  });
@@ -25,13 +25,12 @@ class MigrateRollbackCommand extends Command {
25
25
  const dirEntries = await fs.readdir(migrationsDir);
26
26
  files = dirEntries.filter((entry) => entry.endsWith(".ts")).sort();
27
27
  const migrations = await db.runQuery({
28
- sql: "select * from migrations order by created_at DESC",
29
- bindings: []
28
+ sql: "select * from migrations order by created_at DESC limit $1",
29
+ bindings: [this.steps]
30
30
  });
31
- const count = 0;
32
31
  for (const migration of migrations) {
33
32
  const class_to_migrate = migration.filename;
34
- this.context.stdout.write(`rolling back ${class_to_migrate}`);
33
+ logger().info(`rolling back ${class_to_migrate}`);
35
34
  const ClassToMigrate = (await import(path.join(migrationsDir, class_to_migrate))).default;
36
35
  const c = new ClassToMigrate();
37
36
  await c.down(db.getSchema());
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/app/console/migrate/MigrateRollbackCommand.mts"],"sourcesContent":["import { cli, db as database } from \"../../../facades.mjs\";\nimport { Command, Option } from \"clipanion\";\nimport { context_provider } from \"@devbro/neko-context\";\nimport path from \"path\";\nimport fs from \"fs/promises\";\nimport { config } from \"@devbro/neko-config\";\nimport { Migration } from \"@devbro/neko-sql\";\nimport * as t from \"typanion\";\n\nexport class MigrateRollbackCommand extends Command {\n static paths = [[`migrate`, \"rollback\"]];\n\n steps = Option.String(`--steps`, {\n description: `how many migrations to rollback`,\n validator: t.isNumber(),\n });\n\n async execute() {\n await context_provider.run(async () => {\n // this.context.stdout.write(`Hello Migrate Command!\\n`);\n const db = database();\n const schema = db.getSchema();\n\n const migrationsDir = config.get(\"migration.path\");\n let files: string[] = [];\n\n const dirEntries = await fs.readdir(migrationsDir);\n files = dirEntries.filter((entry) => entry.endsWith(\".ts\")).sort();\n\n const migrations = await db.runQuery({\n sql: \"select * from migrations order by created_at DESC\",\n bindings: [],\n });\n\n const count = 0;\n for (const migration of migrations) {\n const class_to_migrate = migration.filename;\n this.context.stdout.write(`rolling back ${class_to_migrate}`);\n\n const ClassToMigrate = (\n await import(path.join(migrationsDir, class_to_migrate))\n ).default;\n\n const c: Migration = new ClassToMigrate();\n await c.down(db.getSchema());\n await db.runQuery({\n sql: \"delete from migrations where id = $1\",\n bindings: [migration.id],\n });\n }\n });\n }\n}\n\ncli().register(MigrateRollbackCommand);\n"],"mappings":";;AAAA,SAAS,KAAK,MAAM,gBAAgB;AACpC,SAAS,SAAS,cAAc;AAChC,SAAS,wBAAwB;AACjC,OAAO,UAAU;AACjB,OAAO,QAAQ;AACf,SAAS,cAAc;AAEvB,YAAY,OAAO;AAEZ,MAAM,+BAA+B,QAAQ;AAAA,EATpD,OASoD;AAAA;AAAA;AAAA,EAClD,OAAO,QAAQ,CAAC,CAAC,WAAW,UAAU,CAAC;AAAA,EAEvC,QAAQ,OAAO,OAAO,WAAW;AAAA,IAC/B,aAAa;AAAA,IACb,WAAW,EAAE,SAAS;AAAA,EACxB,CAAC;AAAA,EAED,MAAM,UAAU;AACd,UAAM,iBAAiB,IAAI,YAAY;AAErC,YAAM,KAAK,SAAS;AACpB,YAAM,SAAS,GAAG,UAAU;AAE5B,YAAM,gBAAgB,OAAO,IAAI,gBAAgB;AACjD,UAAI,QAAkB,CAAC;AAEvB,YAAM,aAAa,MAAM,GAAG,QAAQ,aAAa;AACjD,cAAQ,WAAW,OAAO,CAAC,UAAU,MAAM,SAAS,KAAK,CAAC,EAAE,KAAK;AAEjE,YAAM,aAAa,MAAM,GAAG,SAAS;AAAA,QACnC,KAAK;AAAA,QACL,UAAU,CAAC;AAAA,MACb,CAAC;AAED,YAAM,QAAQ;AACd,iBAAW,aAAa,YAAY;AAClC,cAAM,mBAAmB,UAAU;AACnC,aAAK,QAAQ,OAAO,MAAM,gBAAgB,gBAAgB,EAAE;AAE5D,cAAM,kBACJ,MAAM,OAAO,KAAK,KAAK,eAAe,gBAAgB,IACtD;AAEF,cAAM,IAAe,IAAI,eAAe;AACxC,cAAM,EAAE,KAAK,GAAG,UAAU,CAAC;AAC3B,cAAM,GAAG,SAAS;AAAA,UAChB,KAAK;AAAA,UACL,UAAU,CAAC,UAAU,EAAE;AAAA,QACzB,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEA,IAAI,EAAE,SAAS,sBAAsB;","names":[]}
1
+ {"version":3,"sources":["../../../../src/app/console/migrate/MigrateRollbackCommand.mts"],"sourcesContent":["import { cli, db as database, logger } from \"../../../facades.mjs\";\nimport { Command, Option } from \"clipanion\";\nimport { context_provider } from \"@devbro/neko-context\";\nimport path from \"path\";\nimport fs from \"fs/promises\";\nimport { config } from \"@devbro/neko-config\";\nimport { Migration } from \"@devbro/neko-sql\";\nimport * as t from \"typanion\";\n\nexport class MigrateRollbackCommand extends Command {\n static paths = [[`migrate`, \"rollback\"]];\n\n steps = Option.String(`--steps`, \"1\", {\n description: `how many migrations to rollback`,\n validator: t.isNumber(),\n });\n\n async execute() {\n await context_provider.run(async () => {\n // this.context.stdout.write(`Hello Migrate Command!\\n`);\n const db = database();\n const schema = db.getSchema();\n\n const migrationsDir = config.get(\"migration.path\");\n let files: string[] = [];\n\n const dirEntries = await fs.readdir(migrationsDir);\n files = dirEntries.filter((entry) => entry.endsWith(\".ts\")).sort();\n\n const migrations = await db.runQuery({\n sql: \"select * from migrations order by created_at DESC limit $1\",\n bindings: [this.steps],\n });\n\n for (const migration of migrations) {\n const class_to_migrate = migration.filename;\n logger().info(`rolling back ${class_to_migrate}`);\n\n const ClassToMigrate = (\n await import(path.join(migrationsDir, class_to_migrate))\n ).default;\n\n const c: Migration = new ClassToMigrate();\n await c.down(db.getSchema());\n await db.runQuery({\n sql: \"delete from migrations where id = $1\",\n bindings: [migration.id],\n });\n }\n });\n }\n}\n\ncli().register(MigrateRollbackCommand);\n"],"mappings":";;AAAA,SAAS,KAAK,MAAM,UAAU,cAAc;AAC5C,SAAS,SAAS,cAAc;AAChC,SAAS,wBAAwB;AACjC,OAAO,UAAU;AACjB,OAAO,QAAQ;AACf,SAAS,cAAc;AAEvB,YAAY,OAAO;AAEZ,MAAM,+BAA+B,QAAQ;AAAA,EATpD,OASoD;AAAA;AAAA;AAAA,EAClD,OAAO,QAAQ,CAAC,CAAC,WAAW,UAAU,CAAC;AAAA,EAEvC,QAAQ,OAAO,OAAO,WAAW,KAAK;AAAA,IACpC,aAAa;AAAA,IACb,WAAW,EAAE,SAAS;AAAA,EACxB,CAAC;AAAA,EAED,MAAM,UAAU;AACd,UAAM,iBAAiB,IAAI,YAAY;AAErC,YAAM,KAAK,SAAS;AACpB,YAAM,SAAS,GAAG,UAAU;AAE5B,YAAM,gBAAgB,OAAO,IAAI,gBAAgB;AACjD,UAAI,QAAkB,CAAC;AAEvB,YAAM,aAAa,MAAM,GAAG,QAAQ,aAAa;AACjD,cAAQ,WAAW,OAAO,CAAC,UAAU,MAAM,SAAS,KAAK,CAAC,EAAE,KAAK;AAEjE,YAAM,aAAa,MAAM,GAAG,SAAS;AAAA,QACnC,KAAK;AAAA,QACL,UAAU,CAAC,KAAK,KAAK;AAAA,MACvB,CAAC;AAED,iBAAW,aAAa,YAAY;AAClC,cAAM,mBAAmB,UAAU;AACnC,eAAO,EAAE,KAAK,gBAAgB,gBAAgB,EAAE;AAEhD,cAAM,kBACJ,MAAM,OAAO,KAAK,KAAK,eAAe,gBAAgB,IACtD;AAEF,cAAM,IAAe,IAAI,eAAe;AACxC,cAAM,EAAE,KAAK,GAAG,UAAU,CAAC;AAC3B,cAAM,GAAG,SAAS;AAAA,UAChB,KAAK;AAAA,UACL,UAAU,CAAC,UAAU,EAAE;AAAA,QACzB,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEA,IAAI,EAAE,SAAS,sBAAsB;","names":[]}
@@ -41,6 +41,9 @@ var import_clipanion2 = require("clipanion");
41
41
  var import_neko_context = require("@devbro/neko-context");
42
42
  var import_errors = require("@devbro/neko-http/errors");
43
43
 
44
+ // ../neko-router/dist/CompiledRoute.mjs
45
+ var import_stream = require("stream");
46
+
44
47
  // ../neko-router/dist/Middleware.mjs
45
48
  var Middleware = class {
46
49
  static {
@@ -118,6 +121,9 @@ var CompiledRoute = class {
118
121
  if (typeof value.toJson === "function") {
119
122
  return traverse(value.toJson());
120
123
  }
124
+ if (typeof value.toJSON === "function") {
125
+ return traverse(value.toJSON());
126
+ }
121
127
  if (Array.isArray(value)) {
122
128
  return value.map(traverse);
123
129
  }
@@ -142,7 +148,7 @@ var CompiledRoute = class {
142
148
  }
143
149
  return String(obj);
144
150
  }
145
- processResponseBody(res, controller_rc) {
151
+ async processResponseBody(res, controller_rc) {
146
152
  if (controller_rc && res.writableEnded) {
147
153
  throw new Error("cannot write to response, response has already ended");
148
154
  }
@@ -151,18 +157,36 @@ var CompiledRoute = class {
151
157
  }
152
158
  if (controller_rc) {
153
159
  const header_content_type = res.getHeader("Content-Type");
154
- if (!header_content_type && typeof controller_rc === "object") {
160
+ if (controller_rc instanceof import_stream.Stream || Buffer.isBuffer(controller_rc)) {
161
+ await this.writeAsync(res, controller_rc);
162
+ res.end();
163
+ } else if (!header_content_type && typeof controller_rc === "object") {
155
164
  res.setHeader("Content-Type", "application/json");
165
+ res.end(this.convertToString(controller_rc));
156
166
  } else if (!header_content_type) {
157
167
  res.setHeader("Content-Type", "text/plain");
168
+ res.end(this.convertToString(controller_rc));
169
+ } else {
170
+ res.end(this.convertToString(controller_rc));
158
171
  }
159
- res.end(this.convertToString(controller_rc));
160
172
  return;
161
173
  } else {
162
174
  res.statusCode = [200].includes(res.statusCode) ? 204 : res.statusCode;
163
175
  res.end();
164
176
  }
165
177
  }
178
+ async writeAsync(res, chunk) {
179
+ return new Promise((resolve, reject) => {
180
+ const ok = res.write(chunk, (err) => {
181
+ if (err) reject(err);
182
+ });
183
+ if (ok) {
184
+ resolve(0);
185
+ } else {
186
+ res.once("drain", resolve);
187
+ }
188
+ });
189
+ }
166
190
  async runMiddlewares(middlewares, req, res) {
167
191
  let index = 0;
168
192
  const me = this;
@@ -220,7 +244,7 @@ var Route = class {
220
244
  i = start;
221
245
  } else if (char === "*") {
222
246
  let start = i + 1;
223
- while (start < path2.length && /[a-zA-Z0-9_]/.test(path2[start])) {
247
+ while (start < path2.length && /[a-zA-Z0-9_\.]/.test(path2[start])) {
224
248
  start++;
225
249
  }
226
250
  tokens.push({ type: "WILDCARD", value: path2.slice(i + 1, start) });
@@ -290,6 +314,10 @@ var Route = class {
290
314
  params: r.groups || {}
291
315
  };
292
316
  }
317
+ prependMiddleware(middlewares) {
318
+ this.middlewares = [].concat(middlewares, this.middlewares);
319
+ return this;
320
+ }
293
321
  addMiddleware(middlewares) {
294
322
  this.middlewares = this.middlewares.concat(middlewares);
295
323
  return this;
@@ -304,6 +332,62 @@ var Route = class {
304
332
 
305
333
  // ../neko-router/dist/Router.mjs
306
334
  var import_path = __toESM(require("path"), 1);
335
+
336
+ // ../node_modules/url-join/lib/url-join.js
337
+ function normalize(strArray) {
338
+ var resultArray = [];
339
+ if (strArray.length === 0) {
340
+ return "";
341
+ }
342
+ if (typeof strArray[0] !== "string") {
343
+ throw new TypeError("Url must be a string. Received " + strArray[0]);
344
+ }
345
+ if (strArray[0].match(/^[^/:]+:\/*$/) && strArray.length > 1) {
346
+ var first = strArray.shift();
347
+ strArray[0] = first + strArray[0];
348
+ }
349
+ if (strArray[0].match(/^file:\/\/\//)) {
350
+ strArray[0] = strArray[0].replace(/^([^/:]+):\/*/, "$1:///");
351
+ } else {
352
+ strArray[0] = strArray[0].replace(/^([^/:]+):\/*/, "$1://");
353
+ }
354
+ for (var i = 0; i < strArray.length; i++) {
355
+ var component = strArray[i];
356
+ if (typeof component !== "string") {
357
+ throw new TypeError("Url must be a string. Received " + component);
358
+ }
359
+ if (component === "") {
360
+ continue;
361
+ }
362
+ if (i > 0) {
363
+ component = component.replace(/^[\/]+/, "");
364
+ }
365
+ if (i < strArray.length - 1) {
366
+ component = component.replace(/[\/]+$/, "");
367
+ } else {
368
+ component = component.replace(/[\/]+$/, "/");
369
+ }
370
+ resultArray.push(component);
371
+ }
372
+ var str = resultArray.join("/");
373
+ str = str.replace(/\/(\?|&|#[^!])/g, "$1");
374
+ var parts = str.split("?");
375
+ str = parts.shift() + (parts.length > 0 ? "?" : "") + parts.join("&");
376
+ return str;
377
+ }
378
+ __name(normalize, "normalize");
379
+ function urlJoin() {
380
+ var input;
381
+ if (typeof arguments[0] === "object") {
382
+ input = arguments[0];
383
+ } else {
384
+ input = [].slice.call(arguments);
385
+ }
386
+ return normalize(input);
387
+ }
388
+ __name(urlJoin, "urlJoin");
389
+
390
+ // ../neko-router/dist/Router.mjs
307
391
  var Router = class {
308
392
  static {
309
393
  __name(this, "Router");
@@ -328,6 +412,12 @@ var Router = class {
328
412
  }).addMiddleware([...controller.baseMiddlewares, ...route.middlewares]);
329
413
  }
330
414
  }
415
+ addRouter(path2, router2) {
416
+ for (const route of router2.routes) {
417
+ let path22 = urlJoin("/", path2, route.path);
418
+ this.addRoute(route.methods, path22, route.handler).addMiddleware(router2.getMiddlewares()).addMiddleware(route.getMiddlewares());
419
+ }
420
+ }
331
421
  addGlobalMiddleware(middlewares) {
332
422
  this.middlewares = this.middlewares.concat(middlewares);
333
423
  }
@@ -44,6 +44,9 @@ var import_path2 = __toESM(require("path"), 1);
44
44
  var import_neko_context = require("@devbro/neko-context");
45
45
  var import_errors = require("@devbro/neko-http/errors");
46
46
 
47
+ // ../neko-router/dist/CompiledRoute.mjs
48
+ var import_stream = require("stream");
49
+
47
50
  // ../neko-router/dist/Middleware.mjs
48
51
  var Middleware = class {
49
52
  static {
@@ -121,6 +124,9 @@ var CompiledRoute = class {
121
124
  if (typeof value.toJson === "function") {
122
125
  return traverse(value.toJson());
123
126
  }
127
+ if (typeof value.toJSON === "function") {
128
+ return traverse(value.toJSON());
129
+ }
124
130
  if (Array.isArray(value)) {
125
131
  return value.map(traverse);
126
132
  }
@@ -145,7 +151,7 @@ var CompiledRoute = class {
145
151
  }
146
152
  return String(obj);
147
153
  }
148
- processResponseBody(res, controller_rc) {
154
+ async processResponseBody(res, controller_rc) {
149
155
  if (controller_rc && res.writableEnded) {
150
156
  throw new Error("cannot write to response, response has already ended");
151
157
  }
@@ -154,18 +160,36 @@ var CompiledRoute = class {
154
160
  }
155
161
  if (controller_rc) {
156
162
  const header_content_type = res.getHeader("Content-Type");
157
- if (!header_content_type && typeof controller_rc === "object") {
163
+ if (controller_rc instanceof import_stream.Stream || Buffer.isBuffer(controller_rc)) {
164
+ await this.writeAsync(res, controller_rc);
165
+ res.end();
166
+ } else if (!header_content_type && typeof controller_rc === "object") {
158
167
  res.setHeader("Content-Type", "application/json");
168
+ res.end(this.convertToString(controller_rc));
159
169
  } else if (!header_content_type) {
160
170
  res.setHeader("Content-Type", "text/plain");
171
+ res.end(this.convertToString(controller_rc));
172
+ } else {
173
+ res.end(this.convertToString(controller_rc));
161
174
  }
162
- res.end(this.convertToString(controller_rc));
163
175
  return;
164
176
  } else {
165
177
  res.statusCode = [200].includes(res.statusCode) ? 204 : res.statusCode;
166
178
  res.end();
167
179
  }
168
180
  }
181
+ async writeAsync(res, chunk) {
182
+ return new Promise((resolve, reject) => {
183
+ const ok = res.write(chunk, (err) => {
184
+ if (err) reject(err);
185
+ });
186
+ if (ok) {
187
+ resolve(0);
188
+ } else {
189
+ res.once("drain", resolve);
190
+ }
191
+ });
192
+ }
169
193
  async runMiddlewares(middlewares, req, res) {
170
194
  let index = 0;
171
195
  const me = this;
@@ -223,7 +247,7 @@ var Route = class {
223
247
  i = start;
224
248
  } else if (char === "*") {
225
249
  let start = i + 1;
226
- while (start < path3.length && /[a-zA-Z0-9_]/.test(path3[start])) {
250
+ while (start < path3.length && /[a-zA-Z0-9_\.]/.test(path3[start])) {
227
251
  start++;
228
252
  }
229
253
  tokens.push({ type: "WILDCARD", value: path3.slice(i + 1, start) });
@@ -293,6 +317,10 @@ var Route = class {
293
317
  params: r.groups || {}
294
318
  };
295
319
  }
320
+ prependMiddleware(middlewares) {
321
+ this.middlewares = [].concat(middlewares, this.middlewares);
322
+ return this;
323
+ }
296
324
  addMiddleware(middlewares) {
297
325
  this.middlewares = this.middlewares.concat(middlewares);
298
326
  return this;
@@ -307,6 +335,62 @@ var Route = class {
307
335
 
308
336
  // ../neko-router/dist/Router.mjs
309
337
  var import_path = __toESM(require("path"), 1);
338
+
339
+ // ../node_modules/url-join/lib/url-join.js
340
+ function normalize(strArray) {
341
+ var resultArray = [];
342
+ if (strArray.length === 0) {
343
+ return "";
344
+ }
345
+ if (typeof strArray[0] !== "string") {
346
+ throw new TypeError("Url must be a string. Received " + strArray[0]);
347
+ }
348
+ if (strArray[0].match(/^[^/:]+:\/*$/) && strArray.length > 1) {
349
+ var first = strArray.shift();
350
+ strArray[0] = first + strArray[0];
351
+ }
352
+ if (strArray[0].match(/^file:\/\/\//)) {
353
+ strArray[0] = strArray[0].replace(/^([^/:]+):\/*/, "$1:///");
354
+ } else {
355
+ strArray[0] = strArray[0].replace(/^([^/:]+):\/*/, "$1://");
356
+ }
357
+ for (var i = 0; i < strArray.length; i++) {
358
+ var component = strArray[i];
359
+ if (typeof component !== "string") {
360
+ throw new TypeError("Url must be a string. Received " + component);
361
+ }
362
+ if (component === "") {
363
+ continue;
364
+ }
365
+ if (i > 0) {
366
+ component = component.replace(/^[\/]+/, "");
367
+ }
368
+ if (i < strArray.length - 1) {
369
+ component = component.replace(/[\/]+$/, "");
370
+ } else {
371
+ component = component.replace(/[\/]+$/, "/");
372
+ }
373
+ resultArray.push(component);
374
+ }
375
+ var str = resultArray.join("/");
376
+ str = str.replace(/\/(\?|&|#[^!])/g, "$1");
377
+ var parts = str.split("?");
378
+ str = parts.shift() + (parts.length > 0 ? "?" : "") + parts.join("&");
379
+ return str;
380
+ }
381
+ __name(normalize, "normalize");
382
+ function urlJoin() {
383
+ var input;
384
+ if (typeof arguments[0] === "object") {
385
+ input = arguments[0];
386
+ } else {
387
+ input = [].slice.call(arguments);
388
+ }
389
+ return normalize(input);
390
+ }
391
+ __name(urlJoin, "urlJoin");
392
+
393
+ // ../neko-router/dist/Router.mjs
310
394
  var Router = class {
311
395
  static {
312
396
  __name(this, "Router");
@@ -331,6 +415,12 @@ var Router = class {
331
415
  }).addMiddleware([...controller.baseMiddlewares, ...route.middlewares]);
332
416
  }
333
417
  }
418
+ addRouter(path22, router2) {
419
+ for (const route of router2.routes) {
420
+ let path222 = urlJoin("/", path22, route.path);
421
+ this.addRoute(route.methods, path222, route.handler).addMiddleware(router2.getMiddlewares()).addMiddleware(route.getMiddlewares());
422
+ }
423
+ }
334
424
  addGlobalMiddleware(middlewares) {
335
425
  this.middlewares = this.middlewares.concat(middlewares);
336
426
  }
@@ -42,6 +42,9 @@ var import_neko_config2 = require("@devbro/neko-config");
42
42
  var import_neko_context = require("@devbro/neko-context");
43
43
  var import_errors = require("@devbro/neko-http/errors");
44
44
 
45
+ // ../neko-router/dist/CompiledRoute.mjs
46
+ var import_stream = require("stream");
47
+
45
48
  // ../neko-router/dist/Middleware.mjs
46
49
  var Middleware = class {
47
50
  static {
@@ -119,6 +122,9 @@ var CompiledRoute = class {
119
122
  if (typeof value.toJson === "function") {
120
123
  return traverse(value.toJson());
121
124
  }
125
+ if (typeof value.toJSON === "function") {
126
+ return traverse(value.toJSON());
127
+ }
122
128
  if (Array.isArray(value)) {
123
129
  return value.map(traverse);
124
130
  }
@@ -143,7 +149,7 @@ var CompiledRoute = class {
143
149
  }
144
150
  return String(obj);
145
151
  }
146
- processResponseBody(res, controller_rc) {
152
+ async processResponseBody(res, controller_rc) {
147
153
  if (controller_rc && res.writableEnded) {
148
154
  throw new Error("cannot write to response, response has already ended");
149
155
  }
@@ -152,18 +158,36 @@ var CompiledRoute = class {
152
158
  }
153
159
  if (controller_rc) {
154
160
  const header_content_type = res.getHeader("Content-Type");
155
- if (!header_content_type && typeof controller_rc === "object") {
161
+ if (controller_rc instanceof import_stream.Stream || Buffer.isBuffer(controller_rc)) {
162
+ await this.writeAsync(res, controller_rc);
163
+ res.end();
164
+ } else if (!header_content_type && typeof controller_rc === "object") {
156
165
  res.setHeader("Content-Type", "application/json");
166
+ res.end(this.convertToString(controller_rc));
157
167
  } else if (!header_content_type) {
158
168
  res.setHeader("Content-Type", "text/plain");
169
+ res.end(this.convertToString(controller_rc));
170
+ } else {
171
+ res.end(this.convertToString(controller_rc));
159
172
  }
160
- res.end(this.convertToString(controller_rc));
161
173
  return;
162
174
  } else {
163
175
  res.statusCode = [200].includes(res.statusCode) ? 204 : res.statusCode;
164
176
  res.end();
165
177
  }
166
178
  }
179
+ async writeAsync(res, chunk) {
180
+ return new Promise((resolve, reject) => {
181
+ const ok = res.write(chunk, (err) => {
182
+ if (err) reject(err);
183
+ });
184
+ if (ok) {
185
+ resolve(0);
186
+ } else {
187
+ res.once("drain", resolve);
188
+ }
189
+ });
190
+ }
167
191
  async runMiddlewares(middlewares, req, res) {
168
192
  let index = 0;
169
193
  const me = this;
@@ -221,7 +245,7 @@ var Route = class {
221
245
  i = start;
222
246
  } else if (char === "*") {
223
247
  let start = i + 1;
224
- while (start < path2.length && /[a-zA-Z0-9_]/.test(path2[start])) {
248
+ while (start < path2.length && /[a-zA-Z0-9_\.]/.test(path2[start])) {
225
249
  start++;
226
250
  }
227
251
  tokens.push({ type: "WILDCARD", value: path2.slice(i + 1, start) });
@@ -291,6 +315,10 @@ var Route = class {
291
315
  params: r.groups || {}
292
316
  };
293
317
  }
318
+ prependMiddleware(middlewares) {
319
+ this.middlewares = [].concat(middlewares, this.middlewares);
320
+ return this;
321
+ }
294
322
  addMiddleware(middlewares) {
295
323
  this.middlewares = this.middlewares.concat(middlewares);
296
324
  return this;
@@ -305,6 +333,62 @@ var Route = class {
305
333
 
306
334
  // ../neko-router/dist/Router.mjs
307
335
  var import_path = __toESM(require("path"), 1);
336
+
337
+ // ../node_modules/url-join/lib/url-join.js
338
+ function normalize(strArray) {
339
+ var resultArray = [];
340
+ if (strArray.length === 0) {
341
+ return "";
342
+ }
343
+ if (typeof strArray[0] !== "string") {
344
+ throw new TypeError("Url must be a string. Received " + strArray[0]);
345
+ }
346
+ if (strArray[0].match(/^[^/:]+:\/*$/) && strArray.length > 1) {
347
+ var first = strArray.shift();
348
+ strArray[0] = first + strArray[0];
349
+ }
350
+ if (strArray[0].match(/^file:\/\/\//)) {
351
+ strArray[0] = strArray[0].replace(/^([^/:]+):\/*/, "$1:///");
352
+ } else {
353
+ strArray[0] = strArray[0].replace(/^([^/:]+):\/*/, "$1://");
354
+ }
355
+ for (var i = 0; i < strArray.length; i++) {
356
+ var component = strArray[i];
357
+ if (typeof component !== "string") {
358
+ throw new TypeError("Url must be a string. Received " + component);
359
+ }
360
+ if (component === "") {
361
+ continue;
362
+ }
363
+ if (i > 0) {
364
+ component = component.replace(/^[\/]+/, "");
365
+ }
366
+ if (i < strArray.length - 1) {
367
+ component = component.replace(/[\/]+$/, "");
368
+ } else {
369
+ component = component.replace(/[\/]+$/, "/");
370
+ }
371
+ resultArray.push(component);
372
+ }
373
+ var str = resultArray.join("/");
374
+ str = str.replace(/\/(\?|&|#[^!])/g, "$1");
375
+ var parts = str.split("?");
376
+ str = parts.shift() + (parts.length > 0 ? "?" : "") + parts.join("&");
377
+ return str;
378
+ }
379
+ __name(normalize, "normalize");
380
+ function urlJoin() {
381
+ var input;
382
+ if (typeof arguments[0] === "object") {
383
+ input = arguments[0];
384
+ } else {
385
+ input = [].slice.call(arguments);
386
+ }
387
+ return normalize(input);
388
+ }
389
+ __name(urlJoin, "urlJoin");
390
+
391
+ // ../neko-router/dist/Router.mjs
308
392
  var Router = class {
309
393
  static {
310
394
  __name(this, "Router");
@@ -329,6 +413,12 @@ var Router = class {
329
413
  }).addMiddleware([...controller.baseMiddlewares, ...route.middlewares]);
330
414
  }
331
415
  }
416
+ addRouter(path2, router2) {
417
+ for (const route of router2.routes) {
418
+ let path22 = urlJoin("/", path2, route.path);
419
+ this.addRoute(route.methods, path22, route.handler).addMiddleware(router2.getMiddlewares()).addMiddleware(route.getMiddlewares());
420
+ }
421
+ }
332
422
  addGlobalMiddleware(middlewares) {
333
423
  this.middlewares = this.middlewares.concat(middlewares);
334
424
  }