bazis 0.97.7 → 0.97.9

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.
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { lstat, mkdir, readFile, realpath, rename, rm, rmdir, writeFile } from "node:fs/promises";
4
4
  import { buildModuleTemplates, type ModuleTemplateFiles, type ModuleTemplateProfile } from "./templates/module";
5
- import { buildPackTemplates } from "./templates/pack";
5
+ import { buildPackTemplates, packPartFolder } from "./templates/pack";
6
6
  import { parseModuleName, type ModuleNaming } from "./naming";
7
7
  import { moduleImportPath, registerModuleInSource } from "./moduleRegistration";
8
8
 
@@ -16,11 +16,16 @@ export interface GenerateModuleOptions {
16
16
  readonly register?: boolean;
17
17
  readonly force?: boolean;
18
18
  readonly profile?: ModuleTemplateProfile;
19
+ /**
20
+ * Composite module to add this module to as a part: `Catalog` puts it into
21
+ * `{modulesRoot}/catalog_modules/<name>_module` and connects it in the pack root.
22
+ */
23
+ readonly pack?: string;
19
24
  /** Validate and return the plan without writing files. */
20
25
  readonly dryRun?: boolean;
21
26
  }
22
27
 
23
- export interface GenerateModulePackOptions extends Omit<GenerateModuleOptions, "profile"> {
28
+ export interface GenerateModulePackOptions extends Omit<GenerateModuleOptions, "profile" | "pack"> {
24
29
  readonly parts: readonly string[];
25
30
  }
26
31
 
@@ -44,7 +49,14 @@ export async function generateModule(options: GenerateModuleOptions): Promise<Ge
44
49
  const naming = parseModuleName(options.name);
45
50
  const profile = options.profile ?? "minimal";
46
51
  if (!["empty", "minimal", "full"].includes(profile)) throw new Error(`Unknown profile: ${profile}`);
52
+ const command = creationCommand("module", options, [`--${profile}`]);
47
53
  const modulesRoot = await canonicalDirectory(path.resolve(options.modulesRoot ?? "src/app/modules"));
54
+ // A part of a composite module lives in its pack and is connected in the pack root.
55
+ const pack = options.pack === undefined ? undefined : await findPack(modulesRoot, options.pack);
56
+ if (pack && options.appModulePath !== undefined) throw new Error("--pack connects the part in the pack root; do not combine it with --app-module.");
57
+ const targetRoot = pack?.dir ?? modulesRoot;
58
+ const folder = pack ? packPartFolder(naming) : naming.folder;
59
+ const target: GenerateModuleOptions = pack ? { ...options, modulesRoot: targetRoot, appModulePath: pack.entry } : options;
48
60
  let authImportPath: string | null | undefined;
49
61
  if (profile === "full") {
50
62
  // The host's auth helpers are optional: without them the controller is
@@ -52,18 +64,46 @@ export async function generateModule(options: GenerateModuleOptions): Promise<Ge
52
64
  const authDir = path.resolve("src/app/modules/auth");
53
65
  const helpers = await Promise.all(["tokenKinds.ts", "jwtAuth.ts"].map(async (helper) => (await fileInfo(path.join(authDir, helper)))?.isFile() === true));
54
66
  authImportPath = helpers.every(Boolean)
55
- ? moduleImportPath(path.join(modulesRoot, naming.folder, "http", `${naming.module}.controller.ts`), await canonicalDirectory(authDir))
67
+ ? moduleImportPath(path.join(targetRoot, folder, "http", `${naming.module}.controller.ts`), await canonicalDirectory(authDir))
56
68
  : null;
57
69
  }
58
- const result = await generateFiles(options, naming, naming.folder, buildModuleTemplates(naming, profile, authImportPath));
59
- return authImportPath === null
60
- ? { ...result, warnings: [...result.warnings, "No auth helpers in src/app/modules/auth: the full profile controller has no @Authorize. Protect its routes before exposing them."] }
61
- : result;
70
+ const templates = buildModuleTemplates(naming, profile, authImportPath, { command, pack: pack?.moduleClass });
71
+ const result = await generateFiles(target, naming, folder, templates);
72
+ const warnings = [...result.warnings];
73
+ if (authImportPath === null) warnings.push("No auth helpers in src/app/modules/auth: the full profile controller has no @Authorize. Protect its routes before exposing them.");
74
+ if (pack) warnings.push(`Add ${naming.moduleClass} to the parts table in ${path.relative(process.cwd(), path.join(pack.dir, "MODULE.md"))}.`);
75
+ return { ...result, warnings };
62
76
  }
63
77
 
64
78
  export async function generateModulePack(options: GenerateModulePackOptions): Promise<GenerateModuleResult> {
65
79
  const naming = parseModuleName(options.name);
66
- return generateFiles(options, naming, `${naming.folder}_modules`, buildPackTemplates(naming, options.parts));
80
+ const command = creationCommand("pack", options, ["--parts", options.parts.join(",")]);
81
+ return generateFiles(options, naming, `${naming.folder}_modules`, buildPackTemplates(naming, options.parts, command));
82
+ }
83
+
84
+ /** The existing composite module `name` under `modulesRoot`: `Catalog` → `catalog_modules/Catalog.module.ts`. */
85
+ async function findPack(modulesRoot: string, name: string): Promise<{ readonly dir: string; readonly entry: string; readonly moduleClass: string }> {
86
+ const naming = parseModuleName(name);
87
+ const dir = path.join(modulesRoot, `${naming.folder}_modules`);
88
+ const entry = path.join(dir, `${naming.module}.module.ts`);
89
+ if ((await fileInfo(entry))?.isFile() !== true) {
90
+ throw new Error(`Pack ${naming.moduleClass} not found: ${path.relative(process.cwd(), entry)}. Create it with bazis g pack ${naming.module} --parts <a,b>.`);
91
+ }
92
+ return { dir, entry, moduleClass: naming.moduleClass };
93
+ }
94
+
95
+ /**
96
+ * The command recorded in the passport, as the architecture rules require. It is
97
+ * rebuilt from the options in a canonical form, so the passport shows the same
98
+ * text however the flags were ordered or aliased.
99
+ */
100
+ function creationCommand(generator: "module" | "pack", options: GenerateModuleOptions, extra: readonly string[]): string {
101
+ const parts = ["bunx bazis g", generator, options.name, ...extra];
102
+ if (options.pack !== undefined) parts.push("--pack", options.pack);
103
+ if (options.modulesRoot !== undefined) parts.push("--modules-root", options.modulesRoot);
104
+ if (options.appModulePath !== undefined) parts.push("--app-module", options.appModulePath);
105
+ if (options.register === false) parts.push("--no-register");
106
+ return parts.join(" ");
67
107
  }
68
108
 
69
109
  async function generateFiles(options: GenerateModuleOptions, naming: ModuleNaming, folder: string, templates: readonly ModuleTemplateFiles[]): Promise<GenerateModuleResult> {
@@ -168,7 +168,7 @@ function buildProjectFiles(name: string, dependency: string, frameworkMode: Fram
168
168
  [".env.example", "# Copy to .env (Bun loads it automatically).\nBAZIS_ENV=development\nHOST=127.0.0.1\nPORT=3000\n"],
169
169
  ["src/app/test/health.test.ts", HEALTH_TEST],
170
170
  ["AGENTS.md", "# Working on this bazis project\n\nBefore changing the application, read the [module architecture](docs/architecture/MODULE_ARCHITECTURE.md). Create new modules only with `bunx bazis g module` or `bunx bazis g pack`; fill in the generated `MODULE.md` afterwards. Only codegen updates `src/generated/`.\n"],
171
- ["docs/architecture/MODULE_ARCHITECTURE.md", "# Application module architecture\n\n`src/index.ts` calls `runApp`; `src/app/modules/App.module.ts` composes feature modules through `imports`. The application root owns no domain logic.\n\nOne self-contained function is an atomic module. It owns its data, services, HTTP and background handlers. A composite module is only for several independent functions; its root does composition. Layers and file counts alone do not create submodules.\n\nCreate new modules only with `bunx bazis g module <Name> --empty|--minimal|--full` or `bunx bazis g pack <Name> --parts <a,b>`. Before implementing, define the responsibility and public entries, then fill in the generated `MODULE.md`: fields, errors, dependencies, exports and checks. Use the public APIs of the `bazis` package and its DI and ORM. Do not edit `src/generated/` by hand; run `bunx bazis codegen`.\n\n`--minimal` creates a sample CRUD with the ORM. To run it the application needs a database provider and a ready schema. For a first function without a database use `--empty`. Check types and the binary build after changes that affect startup.\n"],
171
+ ["docs/architecture/MODULE_ARCHITECTURE.md", "# Application module architecture\n\n`src/index.ts` calls `runApp`; `src/app/modules/App.module.ts` composes feature modules through `imports`. The application root owns no domain logic.\n\nOne self-contained function is an atomic module. It owns its data, services, HTTP and background handlers. A composite module is only for several independent functions; its root does composition. Layers and file counts alone do not create submodules.\n\nCreate new modules only with `bunx bazis g module <Name> --empty|--minimal|--full` or `bunx bazis g pack <Name> --parts <a,b>`; add a part to an existing composite with `bunx bazis g module <Name> --pack <Pack>`. The generated `MODULE.md` records the creation command. Before implementing, define the responsibility and public entries, then fill in the generated `MODULE.md`: fields, errors, dependencies, exports and checks. Use the public APIs of the `bazis` package and its DI and ORM. Do not edit `src/generated/` by hand; run `bunx bazis codegen`.\n\n`--minimal` creates a sample CRUD with the ORM. To run it the application needs a database provider and a ready schema. For a first function without a database use `--empty`. Check types and the binary build after changes that affect startup.\n"],
172
172
  ["src/app/modules/App.module.ts", 'import { Module } from "bazis/core/di";\n\n@Module({ imports: [], exports: [] })\nexport class AppModule {}\n'],
173
173
  ["src/index.ts", 'import { runApp } from "bazis/core/app";\nimport { AppModule } from "./app/modules/App.module";\nimport { registerBazisGeneratedRuntime } from "./generated/bazis/runtime";\n\nawait registerBazisGeneratedRuntime();\nawait runApp(AppModule, { http: { hostname: process.env.HOST ?? "127.0.0.1", port: Number(process.env.PORT ?? 3000), health: true } });\n'],
174
174
  ["README.md", `# ${name}\n\nA [bazis](https://www.npmjs.com/package/bazis) application. ${frameworkMode === "registry"
package/cli/main.ts CHANGED
@@ -26,6 +26,8 @@ Generation options:
26
26
  --link-framework Depend on that local checkout through a file: link (new only)
27
27
  --modules-root <path> Modules root (default: src/app/modules)
28
28
  --app-module <path> Host module (default: {modules-root}/App.module.ts)
29
+ --pack <Name> Module only: add it as a part of the composite <Name>
30
+ ({modules-root}/<name>_modules/<module>_module)
29
31
  --empty Module entry and MODULE.md only
30
32
  --minimal Compact example CRUD and MODULE.md (default)
31
33
  --full Example CRUD/list/cache/auth/background/AI and MODULE.md;
@@ -47,6 +49,7 @@ Examples (from the project root):
47
49
  bunx bazis g m Guest --no-codegen
48
50
  bunx bazis g module Mailer --empty --no-register
49
51
  bunx bazis g pack DataManager --parts tables,fields,validators,records
52
+ bunx bazis g module Prices --empty --pack Catalog
50
53
  bunx bazis codegen --target production
51
54
  bunx bazis dev --watch
52
55
  bunx bazis test
@@ -106,7 +109,7 @@ export async function runCli(argv: readonly string[], runtime: CliRuntime = defa
106
109
  const { generateModule, generateModulePack } = await import("./generateModule");
107
110
  const result = args.generator === "pack"
108
111
  ? await generateModulePack({ ...options, parts: args.parts })
109
- : await generateModule({ ...options, profile: args.profile });
112
+ : await generateModule({ ...options, profile: args.profile, pack: args.pack });
110
113
  runtime.log(`[bazis] ${result.dryRun ? "planned" : "generated"} ${args.generator}: ${result.moduleDir}`);
111
114
  for (const change of result.changes) runtime.log(` ${change.action === "create" ? "+" : "~"} ${change.path}`);
112
115
  for (const warning of result.warnings) runtime.log(`[bazis] ${warning}`);
package/cli/parseCli.ts CHANGED
@@ -8,6 +8,8 @@ export interface ParsedGenerateArgs {
8
8
  readonly parts: readonly string[];
9
9
  readonly modulesRoot?: string;
10
10
  readonly appModule?: string;
11
+ /** Composite module to add the new module to as a part (module generator only). */
12
+ readonly pack?: string;
11
13
  readonly register: boolean;
12
14
  readonly force: boolean;
13
15
  readonly profile: GenerateProfile;
@@ -30,7 +32,7 @@ const GENERATOR_ALIASES = new Map<string, "module" | "pack">([
30
32
  ["module", "module"], ["m", "module"],
31
33
  ["pack", "pack"], ["p", "pack"], ["module-pack", "pack"],
32
34
  ]);
33
- const VALUE_OPTIONS = new Set(["--modules-root", "--app-module", "--parts", "--target", "--path", "--framework", "--outfile"]);
35
+ const VALUE_OPTIONS = new Set(["--modules-root", "--app-module", "--pack", "--parts", "--target", "--path", "--framework", "--outfile"]);
34
36
  const FLAG_OPTIONS = new Set(["--no-register", "--force", "--full", "--enterprise", "--minimal", "--empty", "--dry-run", "--no-codegen", "--link-framework", "--vendor", "--bin", "--watch"]);
35
37
 
36
38
  /** Parsing is pure: help and invalid input can never start generation. */
@@ -119,12 +121,15 @@ export function parseCliArgs(argv: readonly string[]): ParseCliResult {
119
121
  if (profiles.length > 1) return error("Choose only one profile: --empty, --minimal or --full.");
120
122
  if (generator === "pack" && profiles.length > 0) return error("Pack parts start empty; module profile flags are not supported by pack.");
121
123
  if (generator === "module" && options.has("--parts")) return error("--parts is only supported by pack.");
124
+ if (generator === "pack" && options.has("--pack")) return error("--pack adds a part with g module; a pack is created with --parts.");
125
+ if (options.has("--pack") && options.has("--app-module")) return error("--pack connects the part in the pack root; do not combine it with --app-module.");
122
126
  if (options.has("--no-codegen") && target !== undefined) return error("--target cannot be used with --no-codegen.");
123
127
  if (options.has("--no-register") && target !== undefined) return error("Use bazis codegen --target separately after connecting the module.");
124
128
 
125
129
  const parts = options.get("--parts")?.split(",").map((part) => part.trim()) ?? [];
126
130
  try {
127
131
  parseModuleName(name);
132
+ if (options.has("--pack")) parseModuleName(options.get("--pack")!);
128
133
  if (generator === "pack") validatePackParts(parts);
129
134
  } catch (cause) {
130
135
  return error(cause instanceof Error ? cause.message : String(cause));
@@ -133,7 +138,7 @@ export function parseCliArgs(argv: readonly string[]): ParseCliResult {
133
138
  kind: "generate",
134
139
  args: {
135
140
  generator, name, parts,
136
- modulesRoot: options.get("--modules-root"), appModule: options.get("--app-module"),
141
+ modulesRoot: options.get("--modules-root"), appModule: options.get("--app-module"), pack: options.get("--pack"),
137
142
  register: !options.has("--no-register"), force: options.has("--force"),
138
143
  profile: profiles[0] ?? "minimal", dryRun: options.has("--dry-run"),
139
144
  codegen: !options.has("--no-codegen"), target,
@@ -1,5 +1,5 @@
1
1
  import type { ModuleNaming } from "../naming";
2
- import { modulePassport } from "./passport";
2
+ import { modulePassport, type PassportOrigin } from "./passport";
3
3
 
4
4
  export type ModuleTemplateProfile = "empty" | "minimal" | "full";
5
5
 
@@ -20,11 +20,12 @@ export function buildModuleTemplates(
20
20
  profile: ModuleTemplateProfile = "minimal",
21
21
  /** null: the host has no auth helpers, so `full` emits no @Authorize. */
22
22
  authImportPath: string | null = "../../auth",
23
+ origin: PassportOrigin = {},
23
24
  ): readonly ModuleTemplateFiles[] {
24
25
  if (profile === "empty") {
25
26
  return [
26
27
  { relativePath: `${naming.module}.module.ts`, content: `import { Module } from "bazis/core/di";\n\n@Module({ imports: [], exports: [] })\nexport class ${naming.moduleClass} {}\n` },
27
- { relativePath: "MODULE.md", content: modulePassport(naming, profile) },
28
+ { relativePath: "MODULE.md", content: modulePassport(naming, profile, origin) },
28
29
  ];
29
30
  }
30
31
 
@@ -38,7 +39,7 @@ export function buildModuleTemplates(
38
39
  { relativePath: `http/contracts/${naming.module}.requests.ts`, content: requestFile(naming) },
39
40
  { relativePath: `http/contracts/${naming.module}.responses.ts`, content: responseFile(naming) },
40
41
  { relativePath: `http/contracts/${naming.module}List.query.ts`, content: listQueryFile(naming) },
41
- { relativePath: "MODULE.md", content: modulePassport(naming, profile) },
42
+ { relativePath: "MODULE.md", content: modulePassport(naming, profile, origin) },
42
43
  ];
43
44
 
44
45
  if (profile === "minimal") {
@@ -2,12 +2,17 @@ import { parseModuleName, type ModuleNaming } from "../naming";
2
2
  import { validatePackParts } from "../parseCli";
3
3
  import { buildModuleTemplates, type ModuleTemplateFiles } from "./module";
4
4
 
5
- export function buildPackTemplates(naming: ModuleNaming, partNames: readonly string[]): readonly ModuleTemplateFiles[] {
5
+ /** The folder of a pack part: `prices` → `prices_module`. */
6
+ export function packPartFolder(part: ModuleNaming): string {
7
+ return `${part.folder}_module`;
8
+ }
9
+
10
+ export function buildPackTemplates(naming: ModuleNaming, partNames: readonly string[], command?: string): readonly ModuleTemplateFiles[] {
6
11
  validatePackParts(partNames);
7
12
  const parts = partNames.map((name) => {
8
13
  const part = parseModuleName(name);
9
14
  // A part names a responsibility (Tables), not a CRUD entity (Table).
10
- return { ...part, entity: part.module, folder: `${part.folder}_module` };
15
+ return { ...part, entity: part.module, folder: packPartFolder(part) };
11
16
  });
12
17
  if (parts.some((part) => part.moduleClass === naming.moduleClass)) {
13
18
  throw new Error("A pack and its parts must have distinct module class names.");
@@ -32,7 +37,7 @@ export class ${naming.moduleClass} {}
32
37
  Passport version: 1.0. Type: composite.
33
38
  Status: composition scaffold generated; the parts are not implemented yet.
34
39
  Entry: [${naming.module}.module.ts](${naming.module}.module.ts).
35
- Before changing it, read AGENTS.md and docs/architecture/MODULE_ARCHITECTURE.md.
40
+ ${command === undefined ? "" : `Created with: \`${command}\`.\n`}Before changing it, read AGENTS.md and docs/architecture/MODULE_ARCHITECTURE.md.
36
41
 
37
42
  ## Responsibility and parts
38
43
 
@@ -69,7 +74,7 @@ exports, no cycles or duplicate registrations, the parts' tests.
69
74
  A generated scaffold does not prove the pack is ready.
70
75
  `,
71
76
  },
72
- ...parts.flatMap((part) => buildModuleTemplates(part, "empty").map((file) => ({
77
+ ...parts.flatMap((part) => buildModuleTemplates(part, "empty", undefined, { command, pack: naming.moduleClass }).map((file) => ({
73
78
  relativePath: `${part.folder}/${file.relativePath}`, content: file.content,
74
79
  }))),
75
80
  ];
@@ -1,13 +1,22 @@
1
1
  import type { ModuleNaming } from "../naming";
2
2
  import type { ModuleTemplateProfile } from "./module";
3
3
 
4
- export function modulePassport(n: ModuleNaming, profile: ModuleTemplateProfile): string {
4
+ /** Facts the generator knows about how the module was created. */
5
+ export interface PassportOrigin {
6
+ /** The CLI command that created the module, recorded as the architecture rules require. */
7
+ readonly command?: string;
8
+ /** Module class of the composite module this one is a part of. */
9
+ readonly pack?: string;
10
+ }
11
+
12
+ export function modulePassport(n: ModuleNaming, profile: ModuleTemplateProfile, origin: PassportOrigin = {}): string {
13
+ const type = origin.pack === undefined ? "atomic" : `atomic, a part of the composite ${origin.pack} ([passport](../MODULE.md))`;
5
14
  const header = `# ${n.moduleClass}
6
15
 
7
- Passport version: 1.0. Type: atomic. CLI profile: ${profile}.
16
+ Passport version: 1.0. Type: ${type}. CLI profile: ${profile}.
8
17
  Status: scaffold generated; the domain implementation and checks are not done.
9
18
  Entry: [${n.module}.module.ts](${n.module}.module.ts), class ${n.moduleClass}, no arguments.
10
-
19
+ ${origin.command === undefined ? "" : `Created with: \`${origin.command}\`.\n`}
11
20
  Before changing it, read AGENTS.md and docs/architecture/MODULE_ARCHITECTURE.md.
12
21
  This passport covers the scaffold files. The author defines the responsibility
13
22
  and fields before implementing the domain function and updates this passport
@@ -232,7 +232,7 @@ export class CodexAppServerClient implements CodexClient {
232
232
  if (method === "account/updated") this.modelCache = undefined;
233
233
  }, () => { this.clearLogin(); this.modelCache = undefined; });
234
234
  const initialized = await client.request("initialize", {
235
- clientInfo: { name: "bazis", title: "Bazis", version: "0.97.7" }, capabilities: { experimentalApi: true },
235
+ clientInfo: { name: "bazis", title: "Bazis", version: "0.97.9" }, capabilities: { experimentalApi: true },
236
236
  }, 15_000, signal);
237
237
  if (resolve(text(initialized.codexHome, 4096)) !== this.paths.home) throw new CodexError("PROTOCOL_ERROR");
238
238
  client.notify("initialized");
@@ -90,6 +90,19 @@ export function processConfiguration(): Configuration {
90
90
  }
91
91
  return new Configuration(values, origins);
92
92
  }
93
+ /**
94
+ * Why an environment variable name cannot be used for a key, or undefined.
95
+ * Aliases are read from the same BAZIS_* variables as the generated names,
96
+ * so they share the prefix.
97
+ */
98
+ function environmentNameProblem(name: string, claimed: ReadonlySet<string>): string | undefined {
99
+ if (!name.startsWith(ENV_PREFIX)) return `must start with ${ENV_PREFIX}: configuration reads only ${ENV_PREFIX}* variables (for example ${ENV_PREFIX}${name.replace(/^[^A-Za-z0-9]+/, "").toUpperCase() || "NAME"})`;
100
+ if (name.length === ENV_PREFIX.length) return `needs a name after ${ENV_PREFIX}`;
101
+ if (!/^[A-Z0-9_-]+$/.test(name)) return "may contain only A-Z, 0-9, _ and -";
102
+ if (claimed.has(name)) return "is already used by another key of this configuration";
103
+ return undefined;
104
+ }
105
+
93
106
  function isSecret(cell: Cell): cell is SecretSpec { return typeof cell === "object" && cell !== null && "__secret" in cell; }
94
107
  function isEnum(cell: Cell): cell is ConfigEnum { return typeof cell === "object" && cell !== null && "__enum" in cell; }
95
108
  function copyCell(cell: Cell): Cell {
@@ -127,7 +140,8 @@ export function defineConfig(prefixOrSchema: string | ConfigSchema<Defaults>, ma
127
140
  const alias = input.env?.[key];
128
141
  const env = [...new Set([`${ENV_PREFIX}${fullKey(key).replaceAll(".", "__").toUpperCase()}`, ...(typeof alias === "string" ? [alias] : alias ?? [])])];
129
142
  for (const name of env) {
130
- if (!name.startsWith(ENV_PREFIX) || name.length === ENV_PREFIX.length || !/^[A-Z0-9_-]+$/.test(name) || claimed.has(name)) throw new KernelError(`Invalid or duplicate configuration environment name: ${name}.`);
143
+ const problem = environmentNameProblem(name, claimed);
144
+ if (problem) throw new KernelError(`Configuration key "${fullKey(key)}": environment variable "${name}" ${problem}.`);
131
145
  claimed.add(name);
132
146
  }
133
147
  names.set(key, Object.freeze(env));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bazis",
3
- "version": "0.97.7",
3
+ "version": "0.97.9",
4
4
  "description": "Modular backend framework for Bun in TypeScript: DI wired by codegen, HTTP, a PostgreSQL ORM, JWT, WebSocket, agents and a CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Alexander Alenin",