bazis 0.105.3 → 0.107.0
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/cli/build.ts +16 -4
- package/cli/main.ts +3 -1
- package/cli/parseCli.ts +13 -6
- package/core/di/ServiceProvider.ts +38 -0
- package/core/infra/connectors/codex/CodexAppServerClient.ts +1 -1
- package/core/kernel/Kernel.ts +23 -2
- package/core/kernel/KernelBuilder.ts +5 -3
- package/library/terminal/messages.ts +2 -2
- package/package.json +1 -1
package/cli/build.ts
CHANGED
|
@@ -14,9 +14,13 @@ export interface BuildOptions {
|
|
|
14
14
|
readonly outfile?: string;
|
|
15
15
|
/** Let the executable read `.env` files from its working directory (off by default). */
|
|
16
16
|
readonly dotenv?: boolean;
|
|
17
|
+
/** Another platform, `linux-x64`: `bun build --target=bun-linux-x64`. */
|
|
18
|
+
readonly platform?: string;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export interface CompileOptions {
|
|
22
|
+
/** Cross-compile for `bun-<platform>`; Bun downloads that runtime once. */
|
|
23
|
+
readonly platform?: string;
|
|
20
24
|
/** Let the executable read `.env`, `.env.local`, `.env.<NODE_ENV>` from its working directory. */
|
|
21
25
|
readonly dotenv?: boolean;
|
|
22
26
|
}
|
|
@@ -149,10 +153,11 @@ export async function runBuild(cwd: string, options: BuildOptions, codegen: Code
|
|
|
149
153
|
const s = ui.out;
|
|
150
154
|
const t = s.t;
|
|
151
155
|
const startedAt = performance.now();
|
|
152
|
-
const outfile = options.bin ? path.resolve(cwd, options.outfile ?? path.join("bin", await
|
|
156
|
+
const outfile = options.bin ? path.resolve(cwd, options.outfile ?? path.join("bin", await defaultBinaryName(cwd, options.platform))) : undefined;
|
|
153
157
|
commandHeader(s, "build", cwd, [
|
|
154
158
|
s.kv(t.meta.target, s.text(options.bin ? "executable" : "typecheck")),
|
|
155
159
|
...(outfile === undefined ? [] : [s.kv(t.meta.out, s.text(path.relative(cwd, outfile)))]),
|
|
160
|
+
...(options.platform === undefined ? [] : [s.kv(t.meta.platform, s.text(options.platform))]),
|
|
156
161
|
]);
|
|
157
162
|
s.section(t.section.build);
|
|
158
163
|
const generated = await codegen(cwd, "all", { ui });
|
|
@@ -182,7 +187,7 @@ export async function runBuild(cwd: string, options: BuildOptions, codegen: Code
|
|
|
182
187
|
s.section(t.section.binary);
|
|
183
188
|
const stopCompile = s.progress(t.step.compile);
|
|
184
189
|
const compileStarted = performance.now();
|
|
185
|
-
const compiled = await compileBinaryAsync(bun, await projectEntry(cwd), outfile, { dotenv: options.dotenv });
|
|
190
|
+
const compiled = await compileBinaryAsync(bun, await projectEntry(cwd), outfile, { dotenv: options.dotenv, platform: options.platform });
|
|
186
191
|
stopCompile();
|
|
187
192
|
const compileMs = formatDuration(performance.now() - compileStarted);
|
|
188
193
|
const relative = path.relative(cwd, outfile);
|
|
@@ -263,9 +268,10 @@ export async function compileBinaryAsync(bun: string, entry: string, outfile: st
|
|
|
263
268
|
}
|
|
264
269
|
}
|
|
265
270
|
|
|
266
|
-
function compileArgs(bun: string, entry: string, outfile: string, options: CompileOptions): string[] {
|
|
271
|
+
export function compileArgs(bun: string, entry: string, outfile: string, options: CompileOptions): string[] {
|
|
267
272
|
const autoload = ["--no-compile-autoload-bunfig", options.dotenv === true ? "--compile-autoload-dotenv" : "--no-compile-autoload-dotenv"];
|
|
268
|
-
|
|
273
|
+
const target = options.platform === undefined ? [] : [`--target=bun-${options.platform}`];
|
|
274
|
+
return [bun, "build", "--compile", ...target, ...autoload, path.resolve(entry), "--outfile", path.resolve(outfile)];
|
|
269
275
|
}
|
|
270
276
|
|
|
271
277
|
function removeWorkDir(workDir: string): void {
|
|
@@ -287,6 +293,12 @@ async function projectEntry(cwd: string): Promise<string> {
|
|
|
287
293
|
return path.resolve(cwd, entry);
|
|
288
294
|
}
|
|
289
295
|
|
|
296
|
+
/** `bin/<name>`, or `bin/<name>-<platform>` for another platform, so the local build is kept. */
|
|
297
|
+
async function defaultBinaryName(cwd: string, platform: string | undefined): Promise<string> {
|
|
298
|
+
const name = await binaryName(cwd);
|
|
299
|
+
return platform === undefined ? name : `${name}-${platform}`;
|
|
300
|
+
}
|
|
301
|
+
|
|
290
302
|
async function binaryName(cwd: string): Promise<string> {
|
|
291
303
|
const manifest = Bun.file(path.join(cwd, "package.json"));
|
|
292
304
|
const name = await manifest.exists() ? (await manifest.json() as { name?: unknown }).name : undefined;
|
package/cli/main.ts
CHANGED
|
@@ -20,6 +20,8 @@ export const USAGE = `Usage:
|
|
|
20
20
|
bazis build Codegen and typecheck
|
|
21
21
|
bazis build --bin [--outfile <path>] Also compile a standalone executable (default bin/<name>)
|
|
22
22
|
bazis build --bin --dotenv The executable also reads .env files from its working directory
|
|
23
|
+
bazis build --bin --platform <os-arch> For another platform, e.g. linux-x64, linux-arm64-musl
|
|
24
|
+
(default output bin/<name>-<os-arch>)
|
|
23
25
|
bazis --help
|
|
24
26
|
bazis --version Print the bazis version (also -v)
|
|
25
27
|
g can also be written as generate.
|
|
@@ -130,7 +132,7 @@ export async function runCli(argv: readonly string[], runtime: CliRuntime = defa
|
|
|
130
132
|
if (parsed.kind === "codegen") return await runCodegenCommand(cwd, parsed.target, codegen, ui);
|
|
131
133
|
if (parsed.kind === "dev") return await runDev(cwd, codegen, ui, { watch: parsed.watch });
|
|
132
134
|
if (parsed.kind === "test") return await runTest(cwd, parsed.args, codegen, ui);
|
|
133
|
-
if (parsed.kind === "build") return await runBuild(cwd, { bin: parsed.bin, outfile: parsed.outfile, dotenv: parsed.dotenv }, codegen, ui);
|
|
135
|
+
if (parsed.kind === "build") return await runBuild(cwd, { bin: parsed.bin, outfile: parsed.outfile, dotenv: parsed.dotenv, platform: parsed.platform }, codegen, ui);
|
|
134
136
|
if (parsed.kind === "new") return await runNew(parsed, ui);
|
|
135
137
|
// Module registration parses the host module with the TypeScript API: load it
|
|
136
138
|
// only after switching TypeScript 7 to @typescript/typescript6.
|
package/cli/parseCli.ts
CHANGED
|
@@ -26,14 +26,16 @@ export type ParseCliResult =
|
|
|
26
26
|
| { readonly kind: "codegen"; readonly target?: string }
|
|
27
27
|
| { readonly kind: "dev"; readonly watch: boolean }
|
|
28
28
|
| { readonly kind: "test"; readonly args: readonly string[] }
|
|
29
|
-
| { readonly kind: "build"; readonly bin: boolean; readonly outfile?: string; readonly dotenv: boolean }
|
|
29
|
+
| { readonly kind: "build"; readonly bin: boolean; readonly outfile?: string; readonly dotenv: boolean; readonly platform?: string }
|
|
30
30
|
| { readonly kind: "error"; readonly message: string };
|
|
31
31
|
|
|
32
32
|
const GENERATOR_ALIASES = new Map<string, "module" | "pack">([
|
|
33
33
|
["module", "module"], ["m", "module"],
|
|
34
34
|
["pack", "pack"], ["p", "pack"], ["module-pack", "pack"],
|
|
35
35
|
]);
|
|
36
|
-
const VALUE_OPTIONS = new Set(["--modules-root", "--app-module", "--pack", "--parts", "--target", "--path", "--framework", "--outfile"]);
|
|
36
|
+
const VALUE_OPTIONS = new Set(["--modules-root", "--app-module", "--pack", "--parts", "--target", "--path", "--framework", "--outfile", "--platform"]);
|
|
37
|
+
/** Platforms of `bun build --compile --target=bun-<platform>`. */
|
|
38
|
+
export const BINARY_PLATFORM = /^(linux|darwin|windows)-(x64|arm64)(-musl)?(-baseline|-modern)?$/;
|
|
37
39
|
const FLAG_OPTIONS = new Set(["--no-register", "--force", "--full", "--enterprise", "--minimal", "--empty", "--dry-run", "--no-codegen", "--link-framework", "--vendor", "--bin", "--watch", "--dotenv"]);
|
|
38
40
|
|
|
39
41
|
/** Parsing is pure: help and invalid input can never start generation. */
|
|
@@ -91,15 +93,20 @@ export function parseCliArgs(argv: readonly string[]): ParseCliResult {
|
|
|
91
93
|
}
|
|
92
94
|
if (options.has("--watch")) return error("--watch is only supported by dev.");
|
|
93
95
|
if (command === "build") {
|
|
94
|
-
if (positional.length !== 1) return error("Use: bazis build [--bin [--outfile <path>] [--dotenv]]");
|
|
96
|
+
if (positional.length !== 1) return error("Use: bazis build [--bin [--outfile <path>] [--platform <os-arch>] [--dotenv]]");
|
|
95
97
|
for (const option of options.keys()) {
|
|
96
|
-
if (option !== "--bin" && option !== "--outfile" && option !== "--dotenv") return error(`Option ${option} is not supported by build.`);
|
|
98
|
+
if (option !== "--bin" && option !== "--outfile" && option !== "--dotenv" && option !== "--platform") return error(`Option ${option} is not supported by build.`);
|
|
97
99
|
}
|
|
98
100
|
if (options.has("--outfile") && !options.has("--bin")) return error("--outfile requires --bin.");
|
|
99
101
|
if (options.has("--dotenv") && !options.has("--bin")) return error("--dotenv requires --bin.");
|
|
100
|
-
|
|
102
|
+
if (options.has("--platform") && !options.has("--bin")) return error("--platform requires --bin.");
|
|
103
|
+
const platform = options.get("--platform");
|
|
104
|
+
if (platform !== undefined && !BINARY_PLATFORM.test(platform)) {
|
|
105
|
+
return error(`Unknown platform: ${platform}. Use <os>-<arch>[-musl]: linux-x64, linux-arm64, linux-x64-musl, linux-arm64-musl, darwin-arm64, darwin-x64, windows-x64.`);
|
|
106
|
+
}
|
|
107
|
+
return { kind: "build", bin: options.has("--bin"), outfile: options.get("--outfile"), dotenv: options.has("--dotenv"), ...(platform === undefined ? {} : { platform }) };
|
|
101
108
|
}
|
|
102
|
-
if (options.has("--bin") || options.has("--outfile") || options.has("--dotenv")) return error("--bin, --outfile and --dotenv are only supported by build.");
|
|
109
|
+
if (options.has("--bin") || options.has("--outfile") || options.has("--dotenv") || options.has("--platform")) return error("--bin, --outfile, --platform and --dotenv are only supported by build.");
|
|
103
110
|
if (command === "codegen") {
|
|
104
111
|
if (positional.length !== 1) return error("Use: bazis codegen [--target <name|all>]");
|
|
105
112
|
for (const option of options.keys()) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AsyncResolutionRequiredError,
|
|
3
3
|
ClassDependenciesMismatchError,
|
|
4
|
+
DiError,
|
|
4
5
|
InvalidProviderError,
|
|
5
6
|
NamedDependencyNotFoundError,
|
|
6
7
|
ProviderNotFoundError,
|
|
@@ -44,7 +45,27 @@ import {
|
|
|
44
45
|
import type { Token } from "./token";
|
|
45
46
|
import { tokenToDebugName } from "./token";
|
|
46
47
|
|
|
48
|
+
/**
|
|
49
|
+
* `ServiceProvider` as a service in a scope sees that scope, as .NET's
|
|
50
|
+
* IServiceProvider does: `resolve` returns the scope's services and
|
|
51
|
+
* `createScope()` opens a new one. The scope is disposed by whoever created it.
|
|
52
|
+
*/
|
|
53
|
+
class ScopeServiceProvider extends ServiceScope {
|
|
54
|
+
public constructor(provider: ConstructorParameters<typeof ServiceScope>[0], state: ResolutionScopeState, private readonly root: ServiceProvider) {
|
|
55
|
+
super(provider, state);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public createScope(): ServiceScope {
|
|
59
|
+
return this.root.createScope();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public override dispose(): Promise<void> {
|
|
63
|
+
return Promise.reject(new DiError("This ServiceProvider is the scope that resolved the service; whoever created the scope disposes it. To own a scope, call createScope() and dispose that one."));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
47
67
|
export class ServiceProvider implements ServiceResolver {
|
|
68
|
+
private readonly scopeProviders = new WeakMap<ResolutionScopeState, ScopeServiceProvider>();
|
|
48
69
|
private readonly registry: ServiceRegistry;
|
|
49
70
|
private readonly activations = new ResolutionTracker();
|
|
50
71
|
private readonly lifetime: ScopeLifecycle;
|
|
@@ -185,6 +206,17 @@ export class ServiceProvider implements ServiceResolver {
|
|
|
185
206
|
return this.lifetime.disposeScope(scopeState);
|
|
186
207
|
}
|
|
187
208
|
|
|
209
|
+
/** `ServiceProvider` asked for inside a scope: the scope itself, not the root. */
|
|
210
|
+
private providerForScope<T>(token: Token<T>, key: ServiceKey | undefined, scopeState: ResolutionScopeState): T | undefined {
|
|
211
|
+
if (token !== (ServiceProvider as unknown) || key !== undefined || scopeState.isRoot) return undefined;
|
|
212
|
+
let view = this.scopeProviders.get(scopeState);
|
|
213
|
+
if (view === undefined) {
|
|
214
|
+
view = new ScopeServiceProvider(this, scopeState, this);
|
|
215
|
+
this.scopeProviders.set(scopeState, view);
|
|
216
|
+
}
|
|
217
|
+
return view as unknown as T;
|
|
218
|
+
}
|
|
219
|
+
|
|
188
220
|
private resolveSingle<T>(
|
|
189
221
|
token: Token<T>,
|
|
190
222
|
key: ServiceKey | undefined,
|
|
@@ -192,6 +224,8 @@ export class ServiceProvider implements ServiceResolver {
|
|
|
192
224
|
stack: ResolutionActivation[],
|
|
193
225
|
ownerLifetime: ProviderLifetime | undefined,
|
|
194
226
|
): T {
|
|
227
|
+
const scopeProvider = this.providerForScope(token, key, scopeState);
|
|
228
|
+
if (scopeProvider !== undefined) return scopeProvider;
|
|
195
229
|
const registration = this.registry.find(token, key);
|
|
196
230
|
if (!registration) {
|
|
197
231
|
throw new ProviderNotFoundError(token, this.describeResolutionPath(stack, token));
|
|
@@ -278,6 +312,8 @@ export class ServiceProvider implements ServiceResolver {
|
|
|
278
312
|
stack: ResolutionActivation[],
|
|
279
313
|
ownerLifetime?: ProviderLifetime,
|
|
280
314
|
): Promise<T> {
|
|
315
|
+
const scopeProvider = this.providerForScope(token, key, scopeState);
|
|
316
|
+
if (scopeProvider !== undefined) return scopeProvider;
|
|
281
317
|
const registration = this.registry.find(token, key);
|
|
282
318
|
if (!registration) {
|
|
283
319
|
throw new ProviderNotFoundError(token, this.describeResolutionPath(stack, token));
|
|
@@ -733,6 +769,8 @@ export class ServiceProvider implements ServiceResolver {
|
|
|
733
769
|
ownerLifetime: ProviderLifetime | undefined,
|
|
734
770
|
): T | undefined {
|
|
735
771
|
this.lifetime.assertLive(scopeState);
|
|
772
|
+
const scopeProvider = this.providerForScope(token, key, scopeState);
|
|
773
|
+
if (scopeProvider !== undefined) return scopeProvider;
|
|
736
774
|
const registration = this.registry.find(token, key);
|
|
737
775
|
if (!registration) {
|
|
738
776
|
return undefined;
|
|
@@ -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.
|
|
235
|
+
clientInfo: { name: "bazis", title: "Bazis", version: "0.107.0" }, 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/core/kernel/Kernel.ts
CHANGED
|
@@ -261,18 +261,32 @@ export class Kernel {
|
|
|
261
261
|
if (this.startupCleanupFailure !== undefined) return Promise.reject(this.startupCleanupFailure);
|
|
262
262
|
return Promise.resolve();
|
|
263
263
|
}
|
|
264
|
+
// Only an application that started announces its stop.
|
|
265
|
+
const wasStarted = this.state === "started";
|
|
264
266
|
this.state = "stopping";
|
|
265
|
-
this.shutdownPromise = this.stopCore(request);
|
|
267
|
+
this.shutdownPromise = this.stopCore(request, wasStarted);
|
|
266
268
|
return this.shutdownPromise;
|
|
267
269
|
}
|
|
268
270
|
|
|
269
|
-
private async stopCore(request: StopRequest): Promise<void> {
|
|
271
|
+
private async stopCore(request: StopRequest, wasStarted: boolean): Promise<void> {
|
|
270
272
|
// The whole graceful-stop sequence — lifetime notifications, the STOPPING
|
|
271
273
|
// event, hosted services/hooks and disposal — is bounded by one budget. A
|
|
272
274
|
// single hanging callback or handler must not wedge shutdown forever.
|
|
275
|
+
const announce = wasStarted && this.options.startupReport && this.options.schemaOnly !== true;
|
|
276
|
+
const logger = announce ? this.container.tryResolve(LOGGER) : undefined;
|
|
277
|
+
const startedAt = performance.now();
|
|
273
278
|
const work = this.runShutdownSequence(request);
|
|
274
279
|
try {
|
|
275
280
|
await awaitShutdown(work, this.options.shutdownTimeoutMs);
|
|
281
|
+
if (announce) {
|
|
282
|
+
const fields = {
|
|
283
|
+
...(request.signal === undefined ? {} : { signal: request.signal }),
|
|
284
|
+
exitCode: request.exitCode,
|
|
285
|
+
shutdownMs: Number((performance.now() - startedAt).toFixed(1)),
|
|
286
|
+
};
|
|
287
|
+
if (logger) logger.info("bazis stopped", fields);
|
|
288
|
+
else console.log(`[bazis] stopped ${JSON.stringify(fields)}`);
|
|
289
|
+
}
|
|
276
290
|
} finally {
|
|
277
291
|
this.state = "stopped";
|
|
278
292
|
}
|
|
@@ -441,6 +455,12 @@ export class Kernel {
|
|
|
441
455
|
console.error("[bazis] Unhandled error, shutting down:", redacted);
|
|
442
456
|
}
|
|
443
457
|
|
|
458
|
+
/** Where the servers listen: `{ listening: ["http://localhost:3000"] }`. */
|
|
459
|
+
private listening(): { listening?: readonly string[] } {
|
|
460
|
+
const urls = (this.container.tryResolve(StartupReport)?.listeners ?? []).map((listener) => listener.url);
|
|
461
|
+
return urls.length > 0 ? { listening: urls } : {};
|
|
462
|
+
}
|
|
463
|
+
|
|
444
464
|
private printStartupReport(): void {
|
|
445
465
|
if (this.options.onStarted !== undefined) {
|
|
446
466
|
try {
|
|
@@ -460,6 +480,7 @@ export class Kernel {
|
|
|
460
480
|
hostedServices: this.coordinator.startedHostedCount,
|
|
461
481
|
hooks: this.coordinator.hookCount,
|
|
462
482
|
startupMs: Number(this.startupMs.toFixed(1)),
|
|
483
|
+
...this.listening(),
|
|
463
484
|
};
|
|
464
485
|
if (logger) {
|
|
465
486
|
logger.info("bazis started", fields);
|
|
@@ -166,9 +166,11 @@ export class KernelBuilder {
|
|
|
166
166
|
const userRoot = resolveUserRoot(this.rootModule, environment);
|
|
167
167
|
const configs = collectModuleConfigs([userRoot]);
|
|
168
168
|
const configRegistry = new ConfigRegistry(configs, environment.name, configuration);
|
|
169
|
-
|
|
170
|
-
//
|
|
171
|
-
const
|
|
169
|
+
// The `bazis started` / `bazis stopped` lines are printed in every
|
|
170
|
+
// environment: in production they are the only sign that the process is up.
|
|
171
|
+
const startupReport = this.startupReport ?? true;
|
|
172
|
+
// The screen replaces the started line outside production.
|
|
173
|
+
const screen = this.startupScreen !== undefined && (this.startupReport ?? !environment.isProduction);
|
|
172
174
|
const report = new StartupReport(screen);
|
|
173
175
|
report.configuration(configRegistry.inspect());
|
|
174
176
|
this.builtReport = report;
|
|
@@ -98,7 +98,7 @@ const en = {
|
|
|
98
98
|
codegen: "codegen", typecheck: "typecheck", config: "config", connections: "connections", schema: "schema",
|
|
99
99
|
modules: "modules", services: "services", tests: "bun test", compile: "compile", files: "files", register: "register",
|
|
100
100
|
},
|
|
101
|
-
meta: { env: "env", watch: "watch", on: "on", off: "off", src: "src", pid: "pid", target: "target", out: "output", path: "path" },
|
|
101
|
+
meta: { env: "env", watch: "watch", on: "on", off: "off", src: "src", pid: "pid", target: "target", out: "output", path: "path", platform: "platform" },
|
|
102
102
|
common: {
|
|
103
103
|
of: "of", in: "in", and: "and", none: "none", ok: "ok", empty: "empty", plus: "plus", memory: "memory",
|
|
104
104
|
declaredIn: "declared in", code: "code", where: "where", try: "try", logs: "logs", inProcess: "in process",
|
|
@@ -260,7 +260,7 @@ const ru: Messages = {
|
|
|
260
260
|
codegen: "кодогенерация", typecheck: "типы", config: "конфигурация", connections: "подключения", schema: "схема",
|
|
261
261
|
modules: "модули", services: "сервисы", tests: "bun test", compile: "компиляция", files: "файлы", register: "подключение",
|
|
262
262
|
},
|
|
263
|
-
meta: { env: "среда", watch: "слежение", on: "вкл", off: "выкл", src: "вход", pid: "pid", target: "цель", out: "файл", path: "путь" },
|
|
263
|
+
meta: { env: "среда", watch: "слежение", on: "вкл", off: "выкл", src: "вход", pid: "pid", target: "цель", out: "файл", path: "путь", platform: "платформа" },
|
|
264
264
|
common: {
|
|
265
265
|
of: "из", in: "в", and: "и", none: "нет", ok: "в порядке", empty: "пусто", plus: "ещё", memory: "память",
|
|
266
266
|
declaredIn: "объявлено в", code: "код", where: "где", try: "попробуйте", logs: "журнал", inProcess: "в процессе",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bazis",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.107.0",
|
|
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",
|