bazis 0.96.1 → 0.96.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,18 +7,58 @@ per-environment defaults, JWT, WebSocket, gRPC, background services, AI agents
7
7
  and a CLI that scaffolds, runs, tests and compiles your app into a single
8
8
  executable.
9
9
 
10
- The package ships TypeScript sources and runs on Bun only (≥ 1.4.0). There is
11
- no build step and no separate type package.
10
+ ## Requirements
12
11
 
13
- ## Quick start
12
+ - **Bun 1.4.0 or newer. Node.js and Deno are not supported.** The package
13
+ ships TypeScript sources with standard decorators and no build step; Bun
14
+ runs them directly. Node.js refuses to load TypeScript from `node_modules`
15
+ (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`), and the runtime uses Bun
16
+ APIs throughout: `Bun.serve`, `Bun.SQL`, the built-in Redis client,
17
+ `bun:test`, `bun build --compile`.
18
+ - TypeScript 5.9 or newer as a peer dependency: the code generator reads
19
+ your sources through the TypeScript compiler API. Nothing is compiled to
20
+ JavaScript.
21
+ - PostgreSQL for the ORM.
22
+ - Qualified platforms: macOS arm64, Linux x64 and arm64.
23
+
24
+ ## Install
25
+
26
+ The package is `bazis` on npm. Install
27
+ it with Bun; `npm install` would download it too, but the code only runs on Bun.
28
+
29
+ **A new project** (recommended): the CLI creates the app and adds the
30
+ dependency `"bazis": "^<current version>"`:
14
31
 
15
32
  ```sh
16
- bunx bazis new MyApp
33
+ bunx bazis new MyApp # creates ./my-app; nothing is copied, the package comes from npm
17
34
  cd my-app
18
- bun install
35
+ bun install # downloads bazis and its peer dependency typescript
19
36
  bunx bazis dev # GET http://127.0.0.1:3000/health
20
37
  ```
21
38
 
39
+ **An existing Bun project:**
40
+
41
+ ```sh
42
+ bun add bazis # or a fixed version: bun add bazis@0.96.2
43
+ bun add -d typescript @types/bun
44
+ ```
45
+
46
+ Then take `tsconfig.json`, `bazis.config.json` and `src/index.ts` from a
47
+ project made by `bazis new` as the starting point, and run `bunx bazis codegen`.
48
+
49
+ | Task | Command |
50
+ | --- | --- |
51
+ | Show the installed version | `bun pm ls \| grep bazis` |
52
+ | Update within the version range | `bun update bazis` |
53
+ | Move to the latest release | `bun add bazis@latest` |
54
+ | A project that must build without npm access | `bunx bazis new MyApp --vendor` (copies the package into `vendor/bazis`) |
55
+
56
+ Every release is published from GitHub Actions with
57
+ [npm provenance](https://docs.npmjs.com/generating-provenance-statements): the
58
+ npm page shows the source commit and the workflow that built it. The release
59
+ tarball is also attached to each
60
+ [GitHub Release](https://github.com/asus97178/bazis/releases).
61
+
22
62
  `bazis new` creates a project with `src/index.ts`, a root `AppModule`,
23
63
  `bazis.config.json`, scripts, a `/health` test and an `.env.example`.
24
64
 
@@ -26,7 +66,7 @@ bunx bazis dev # GET http://127.0.0.1:3000/health
26
66
 
27
67
  | Command | What it does |
28
68
  | --- | --- |
29
- | `bazis new <Name>` | Create a project in `./<name>` |
69
+ | `bazis new <Name> [--vendor]` | Create a project in `./<name>` that depends on `bazis` from npm; `--vendor` copies the package into `vendor/bazis` instead |
30
70
  | `bazis g module <Name> --empty\|--minimal\|--full` | Add a module: empty; CRUD with ORM, validation and paging; or the same with auth guards |
31
71
  | `bazis g pack <Name> --parts a,b` | Add a composite module made of several atomic ones |
32
72
  | `bazis codegen` | Regenerate dependency wiring into `src/generated/bazis` |
@@ -8,8 +8,10 @@ export interface GenerateProjectOptions {
8
8
  readonly outputPath?: string;
9
9
  /** Local Bazis package source; defaults to this checkout's src/bazis. */
10
10
  readonly frameworkPath?: string;
11
- /** Opt into a live link to the source checkout instead of a portable snapshot. */
11
+ /** Opt into a live link to the source checkout instead of the npm dependency. */
12
12
  readonly linkFramework?: boolean;
13
+ /** Copy the package into vendor/bazis instead of depending on npm (offline projects). */
14
+ readonly vendor?: boolean;
13
15
  readonly dryRun?: boolean;
14
16
  }
15
17
 
@@ -18,8 +20,11 @@ export interface GenerateProjectResult {
18
20
  /** Application scaffold files; framework sources are summarized separately. */
19
21
  readonly files: readonly string[];
20
22
  readonly dryRun: boolean;
21
- readonly frameworkMode: "snapshot" | "link";
23
+ /** registry: `"bazis": "^<version>"` from npm; snapshot: vendor/bazis copy; link: `file:` to a checkout. */
24
+ readonly frameworkMode: FrameworkMode;
22
25
  readonly frameworkFileCount: number;
26
+ /** The dependency written to package.json. */
27
+ readonly dependency: string;
23
28
  }
24
29
 
25
30
  /** Create a separate application without mutating the framework checkout. */
@@ -35,16 +40,17 @@ export async function generateProject(options: GenerateProjectOptions): Promise<
35
40
  const projectDir = path.join(parent, path.basename(requested));
36
41
  if (await exists(projectDir)) throw new Error(`Project path already exists: ${projectDir}`);
37
42
 
43
+ if (options.linkFramework && options.vendor) throw new Error("--link-framework and --vendor cannot be combined.");
38
44
  const frameworkDir = await resolveFramework(options.frameworkPath);
39
45
  if (projectDir.startsWith(`${frameworkDir}${path.sep}`)) {
40
46
  throw new Error("Project directory must be outside the Bazis package.");
41
47
  }
42
48
  const dependencyPath = path.relative(projectDir, frameworkDir).replaceAll("\\", "/");
43
- const frameworkMode = options.linkFramework ? "link" : "snapshot";
44
- const frameworkFiles = options.linkFramework ? [] : await collectFrameworkFiles(frameworkDir);
45
- const dependency = options.linkFramework
49
+ const frameworkMode: FrameworkMode = options.linkFramework ? "link" : options.vendor ? "snapshot" : "registry";
50
+ const frameworkFiles = frameworkMode === "snapshot" ? await collectFrameworkFiles(frameworkDir) : [];
51
+ const dependency = frameworkMode === "link"
46
52
  ? `file:${dependencyPath.startsWith(".") ? dependencyPath : `./${dependencyPath}`}`
47
- : "file:./vendor/bazis";
53
+ : frameworkMode === "snapshot" ? "file:./vendor/bazis" : `^${await frameworkVersion(frameworkDir)}`;
48
54
  const files = buildProjectFiles(name, dependency, frameworkMode);
49
55
  if (!options.dryRun) {
50
56
  const staged = await mkdtemp(path.join(parent, `.${name}.bazis-`));
@@ -67,7 +73,16 @@ export async function generateProject(options: GenerateProjectOptions): Promise<
67
73
  }
68
74
  }
69
75
  return { projectDir, files: files.map(([relative]) => path.join(projectDir, relative)), dryRun: options.dryRun === true,
70
- frameworkMode, frameworkFileCount: frameworkFiles.length };
76
+ frameworkMode, frameworkFileCount: frameworkFiles.length, dependency };
77
+ }
78
+
79
+ export type FrameworkMode = "registry" | "snapshot" | "link";
80
+
81
+ /** The version the CLI belongs to; a new project depends on the same release line. */
82
+ async function frameworkVersion(directory: string): Promise<string> {
83
+ const version = JSON.parse(await readFile(path.join(directory, "package.json"), "utf8")).version;
84
+ if (typeof version !== "string" || !/^\d+\.\d+\.\d+$/.test(version)) throw new Error(`Bazis package has no release version: ${String(version)}`);
85
+ return version;
71
86
  }
72
87
 
73
88
  /** Package sources only: do not carry checkout state, dependencies or test fixtures. */
@@ -122,7 +137,7 @@ async function exists(file: string): Promise<boolean> {
122
137
  }
123
138
  }
124
139
 
125
- function buildProjectFiles(name: string, dependency: string, frameworkMode: "snapshot" | "link"): readonly (readonly [string, string])[] {
140
+ function buildProjectFiles(name: string, dependency: string, frameworkMode: FrameworkMode): readonly (readonly [string, string])[] {
126
141
  const manifest = {
127
142
  name, version: "0.1.0", private: true, type: "module",
128
143
  scripts: {
@@ -156,7 +171,9 @@ function buildProjectFiles(name: string, dependency: string, frameworkMode: "sna
156
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"],
157
172
  ["src/app/modules/App.module.ts", 'import { Module } from "bazis/core/di";\n\n@Module({ imports: [], exports: [] })\nexport class AppModule {}\n'],
158
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'],
159
- ["README.md", `# ${name}\n\nAn [bazis](https://www.npmjs.com/package/bazis) application. ${frameworkMode === "snapshot"
174
+ ["README.md", `# ${name}\n\nA [bazis](https://www.npmjs.com/package/bazis) application. ${frameworkMode === "registry"
175
+ ? "The framework comes from npm as the `bazis` dependency; update it with `bun update bazis`."
176
+ : frameworkMode === "snapshot"
160
177
  ? "The framework package is copied to `vendor/bazis`; keep it in Git and move it with the project. The framework checkout is no longer needed. Framework updates do not reach this copy automatically."
161
178
  : "The `bazis` dependency links an external local checkout (`--link-framework`). Moving the project needs the same package and an updated path in `package.json`."}\nRead the [local architecture](docs/architecture/MODULE_ARCHITECTURE.md) before changing modules.\n\nNeeds Bun ≥ 1.4.0. Settings go to \`.env\` (example: \`.env.example\`).\n\n\`\`\`sh\nbun install\nbunx bazis dev\n# GET http://127.0.0.1:3000/health\n# If the port is busy: PORT=3100 bunx bazis dev\n\`\`\`\n\nAdd an atomic module from the project root: \`bunx bazis g module Task --empty\`.\nAfter filling in its passport and implementation, run \`bunx bazis codegen\`.\nTests: \`bunx bazis test\`. Typecheck: \`bunx bazis build\`. Binary: \`bunx bazis build --bin\`, run it with \`bun run start\`.\n\n\`--minimal\` creates a sample CRUD with paging (20 records by default, at most 100 over HTTP); it needs a database provider and a schema to run. The service returns \`PageResult\`; the HTTP controller builds JSON:API. \`DbContext.saveChanges()\` saves every change of its context. In ORM predicates combine conditions with \`.and()\` and \`.or()\`; codegen rejects \`&&\` and \`||\` between predicates.\n`],
162
179
  ];
package/cli/main.ts CHANGED
@@ -22,7 +22,8 @@ export const USAGE = `Usage:
22
22
  Generation options:
23
23
  --path <directory> Exact project directory (new only; default: ./<name>)
24
24
  --framework <path> Local bazis package directory (new only)
25
- --link-framework Link that checkout instead of copying vendor/bazis (new only)
25
+ --vendor Copy the package into vendor/bazis instead of the npm dependency (new only)
26
+ --link-framework Depend on that local checkout through a file: link (new only)
26
27
  --modules-root <path> Modules root (default: src/app/modules)
27
28
  --app-module <path> Host module (default: {modules-root}/App.module.ts)
28
29
  --empty Module entry and MODULE.md only
@@ -83,10 +84,12 @@ export async function runCli(argv: readonly string[], runtime: CliRuntime = defa
83
84
  if (parsed.kind === "test") return await runTest(process.cwd(), parsed.args, runtime.codegen);
84
85
  if (parsed.kind === "build") return await runBuild(process.cwd(), { bin: parsed.bin, outfile: parsed.outfile }, runtime.codegen, runtime.log);
85
86
  if (parsed.kind === "new") {
86
- const result = await generateProject({ name: parsed.name, outputPath: parsed.outputPath, frameworkPath: parsed.frameworkPath, linkFramework: parsed.linkFramework, dryRun: parsed.dryRun });
87
+ const result = await generateProject({ name: parsed.name, outputPath: parsed.outputPath, frameworkPath: parsed.frameworkPath, linkFramework: parsed.linkFramework, vendor: parsed.vendor, dryRun: parsed.dryRun });
87
88
  runtime.log(`[bazis] ${result.dryRun ? "planned" : "created"} project: ${result.projectDir}`);
88
89
  for (const file of result.files) runtime.log(` + ${file}`);
89
- runtime.log(result.frameworkMode === "snapshot"
90
+ runtime.log(result.frameworkMode === "registry"
91
+ ? `[bazis] Framework dependency: "bazis": "${result.dependency}" from npm.`
92
+ : result.frameworkMode === "snapshot"
90
93
  ? `[bazis] vendor/bazis: ${result.frameworkFileCount} package files${result.dryRun ? " planned" : " copied"}. Keep this directory in version control.`
91
94
  : "[bazis] Framework is linked to an external checkout (--link-framework).");
92
95
  if (!result.dryRun) runtime.log(`[bazis] Next: cd ${result.projectDir} && bun install && bunx bazis dev`);
package/cli/parseCli.ts CHANGED
@@ -18,7 +18,7 @@ export interface ParsedGenerateArgs {
18
18
 
19
19
  export type ParseCliResult =
20
20
  | { readonly kind: "help"; readonly help: boolean }
21
- | { readonly kind: "new"; readonly name: string; readonly outputPath?: string; readonly frameworkPath?: string; readonly linkFramework: boolean; readonly dryRun: boolean }
21
+ | { readonly kind: "new"; readonly name: string; readonly outputPath?: string; readonly frameworkPath?: string; readonly linkFramework: boolean; readonly vendor: boolean; readonly dryRun: boolean }
22
22
  | { readonly kind: "generate"; readonly args: ParsedGenerateArgs }
23
23
  | { readonly kind: "codegen"; readonly target?: string }
24
24
  | { readonly kind: "dev"; readonly watch: boolean }
@@ -31,7 +31,7 @@ const GENERATOR_ALIASES = new Map<string, "module" | "pack">([
31
31
  ["pack", "pack"], ["p", "pack"], ["module-pack", "pack"],
32
32
  ]);
33
33
  const VALUE_OPTIONS = new Set(["--modules-root", "--app-module", "--parts", "--target", "--path", "--framework", "--outfile"]);
34
- const FLAG_OPTIONS = new Set(["--no-register", "--force", "--full", "--enterprise", "--minimal", "--empty", "--dry-run", "--no-codegen", "--link-framework", "--bin", "--watch"]);
34
+ const FLAG_OPTIONS = new Set(["--no-register", "--force", "--full", "--enterprise", "--minimal", "--empty", "--dry-run", "--no-codegen", "--link-framework", "--vendor", "--bin", "--watch"]);
35
35
 
36
36
  /** Parsing is pure: help and invalid input can never start generation. */
37
37
  export function parseCliArgs(argv: readonly string[]): ParseCliResult {
@@ -64,18 +64,19 @@ export function parseCliArgs(argv: readonly string[]): ParseCliResult {
64
64
  const [command, generatorToken, name] = positional;
65
65
  const target = options.get("--target");
66
66
  if (command === "new") {
67
- if (positional.length !== 2) return error("Use: bazis new <Name> [--path <directory>] [--framework <directory>] [--link-framework] [--dry-run]");
67
+ if (positional.length !== 2) return error("Use: bazis new <Name> [--path <directory>] [--framework <directory>] [--vendor | --link-framework] [--dry-run]");
68
68
  for (const option of options.keys()) {
69
- if (option !== "--path" && option !== "--framework" && option !== "--link-framework" && option !== "--dry-run") {
69
+ if (option !== "--path" && option !== "--framework" && option !== "--link-framework" && option !== "--vendor" && option !== "--dry-run") {
70
70
  return error(`Option ${option} is not supported by new.`);
71
71
  }
72
72
  }
73
+ if (options.has("--vendor") && options.has("--link-framework")) return error("--vendor and --link-framework cannot be combined.");
73
74
  try {
74
75
  parseModuleName(generatorToken!);
75
76
  } catch (cause) {
76
77
  return error(cause instanceof Error ? cause.message : String(cause));
77
78
  }
78
- return { kind: "new", name: generatorToken!, outputPath: options.get("--path"), frameworkPath: options.get("--framework"), linkFramework: options.has("--link-framework"), dryRun: options.has("--dry-run") };
79
+ return { kind: "new", name: generatorToken!, outputPath: options.get("--path"), frameworkPath: options.get("--framework"), linkFramework: options.has("--link-framework"), vendor: options.has("--vendor"), dryRun: options.has("--dry-run") };
79
80
  }
80
81
  if (target !== undefined && !/^[a-z][a-z0-9-]*$/.test(target)) {
81
82
  return error("Target must be a codegen target name or all.");
@@ -105,7 +106,7 @@ export function parseCliArgs(argv: readonly string[]): ParseCliResult {
105
106
  return error(`Unknown command: ${command ?? "(missing)"}. Use: bazis --help`);
106
107
  }
107
108
  if (generatorToken === undefined) return error("Generator is required: module (m) or pack (p).");
108
- if (options.has("--path") || options.has("--framework") || options.has("--link-framework")) return error("--path, --framework and --link-framework are only supported by new.");
109
+ if (options.has("--path") || options.has("--framework") || options.has("--link-framework") || options.has("--vendor")) return error("--path, --framework, --link-framework and --vendor are only supported by new.");
109
110
  const generator = GENERATOR_ALIASES.get(generatorToken);
110
111
  if (generator === undefined) return error(`Unknown generator: ${generatorToken}. Supported: module (m), pack (p)`);
111
112
  if (name === undefined) return error("Module name is required.");
@@ -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.96.1" }, capabilities: { experimentalApi: true },
235
+ clientInfo: { name: "bazis", title: "Bazis", version: "0.96.2" }, 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");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bazis",
3
- "version": "0.96.1",
3
+ "version": "0.96.2",
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",