bazis 0.97.3 → 0.97.5
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 +16 -4
- package/cli/codegen.ts +4 -1
- package/cli/main.ts +5 -1
- package/core/di/ServiceProvider.ts +17 -0
- package/core/infra/connectors/codex/CodexAppServerClient.ts +1 -1
- package/core/kernel/Kernel.ts +4 -0
- package/core/scripts/di-generate.ts +7 -1
- package/core/scripts/typescriptApi.ts +61 -0
- package/core/scripts/typescriptApiPreload.ts +9 -0
- package/library/orm/Providers/OwnedStoreCatalog.reader.ts +7 -3
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -15,9 +15,10 @@ executable.
|
|
|
15
15
|
(`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`), and the runtime uses Bun
|
|
16
16
|
APIs throughout: `Bun.serve`, `Bun.SQL`, the built-in Redis client,
|
|
17
17
|
`bun:test`, `bun build --compile`.
|
|
18
|
-
- TypeScript 5.9 or
|
|
19
|
-
your sources through the TypeScript compiler API.
|
|
20
|
-
|
|
18
|
+
- TypeScript 5.9, 6 or 7 as a peer dependency: the code generator reads
|
|
19
|
+
your sources through the TypeScript compiler API. With TypeScript 7, which
|
|
20
|
+
has no such API, also install `@typescript/typescript6`. Nothing is
|
|
21
|
+
compiled to JavaScript.
|
|
21
22
|
- PostgreSQL for the ORM.
|
|
22
23
|
- Qualified platforms: macOS arm64, Linux x64 and arm64.
|
|
23
24
|
|
|
@@ -40,9 +41,20 @@ bunx bazis dev # GET http://127.0.0.1:3000/health
|
|
|
40
41
|
|
|
41
42
|
```sh
|
|
42
43
|
bun add bazis # or a fixed version: bun add bazis@0.96.2
|
|
43
|
-
bun add -d typescript@^6 @types/bun
|
|
44
|
+
bun add -d typescript@^6 @types/bun
|
|
44
45
|
```
|
|
45
46
|
|
|
47
|
+
TypeScript 7 also works. It ships no compiler API, which the code generator
|
|
48
|
+
needs, so add Microsoft's TypeScript 6 API package next to it:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
bun add -d typescript@^7 @typescript/typescript6 @types/bun
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The project keeps type-checking with TypeScript 7; only codegen reads the
|
|
55
|
+
sources through the TypeScript 6 API. Without `@typescript/typescript6`
|
|
56
|
+
codegen stops with `BAZIS_TYPESCRIPT_API_MISSING` and this install command.
|
|
57
|
+
|
|
46
58
|
Then take `tsconfig.json`, `bazis.config.json` and `src/index.ts` from a
|
|
47
59
|
project made by `bazis new` as the starting point, and run `bunx bazis codegen`.
|
|
48
60
|
|
package/cli/codegen.ts
CHANGED
|
@@ -11,7 +11,10 @@ export async function runCodegen(cwd: string, target?: string): Promise<number>
|
|
|
11
11
|
const config = await configFile.json();
|
|
12
12
|
if (target !== "all" && !Object.hasOwn(config.targets ?? {}, target)) throw new Error(`Unknown codegen target: ${target}`);
|
|
13
13
|
}
|
|
14
|
-
const
|
|
14
|
+
const generator = resolveGenerator(cwd);
|
|
15
|
+
// TypeScript 7 has no compiler API: the preload switches it to @typescript/typescript6.
|
|
16
|
+
const preload = path.join(path.dirname(generator), "typescriptApiPreload.ts");
|
|
17
|
+
const args = [await resolveBun(cwd), ...(existsSync(preload) ? ["--preload", preload] : []), generator, ...(target === undefined ? [] : ["--target", target])];
|
|
15
18
|
return await Bun.spawn(args, { cwd, stdout: "inherit", stderr: "inherit" }).exited;
|
|
16
19
|
}
|
|
17
20
|
|
package/cli/main.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import {
|
|
2
|
+
import { useTypeScriptCompilerApi } from "../core/scripts/typescriptApi";
|
|
3
3
|
import { parseCliArgs } from "./parseCli";
|
|
4
4
|
import { runCodegen } from "./codegen";
|
|
5
5
|
import { generateProject } from "./generateProject";
|
|
@@ -100,6 +100,10 @@ export async function runCli(argv: readonly string[], runtime: CliRuntime = defa
|
|
|
100
100
|
name: args.name, modulesRoot: args.modulesRoot, appModulePath: args.appModule,
|
|
101
101
|
register: args.register, force: args.force, dryRun: args.dryRun,
|
|
102
102
|
};
|
|
103
|
+
// Module registration parses the host module with the TypeScript API: load it
|
|
104
|
+
// only after switching TypeScript 7 to @typescript/typescript6.
|
|
105
|
+
useTypeScriptCompilerApi(process.cwd());
|
|
106
|
+
const { generateModule, generateModulePack } = await import("./generateModule");
|
|
103
107
|
const result = args.generator === "pack"
|
|
104
108
|
? await generateModulePack({ ...options, parts: args.parts })
|
|
105
109
|
: await generateModule({ ...options, profile: args.profile });
|
|
@@ -112,6 +112,23 @@ export class ServiceProvider implements ServiceResolver {
|
|
|
112
112
|
return this.resolveSingleAsync(token, key, this.lifetime.root, []);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Creates every singleton registered with an async factory (keyed ones
|
|
117
|
+
* included), in registration order. Afterwards `resolve` and constructor
|
|
118
|
+
* injection return the cached instances. The kernel calls it at startup, so a
|
|
119
|
+
* failing factory stops the start instead of failing every request.
|
|
120
|
+
*/
|
|
121
|
+
public async initializeAsyncSingletons(): Promise<void> {
|
|
122
|
+
this.lifetime.assertLive(this.lifetime.root);
|
|
123
|
+
for (const group of this.registry.groups()) {
|
|
124
|
+
for (const registration of group) {
|
|
125
|
+
if (registration.lifetime === "singleton" && isAsyncFactoryProvider(registration.provider)) {
|
|
126
|
+
await this.resolveRegistrationAsync(registration, registration.key, this.lifetime.root, [], undefined);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
115
132
|
public tryResolve<T>(token: Token<T>, key?: ServiceKey): T | undefined {
|
|
116
133
|
return this.tryResolveForScope(token, key, this.lifetime.root);
|
|
117
134
|
}
|
|
@@ -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.
|
|
235
|
+
clientInfo: { name: "bazis", title: "Bazis", version: "0.97.5" }, 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
|
@@ -13,6 +13,7 @@ import type { UnhandledErrorPolicy } from "./types";
|
|
|
13
13
|
import { redactSensitive } from "../../library/redaction";
|
|
14
14
|
import { validateTimeout } from "./internal/validateTimeout";
|
|
15
15
|
import { awaitShutdown } from "./internal/awaitShutdown";
|
|
16
|
+
import { awaitAbortable } from "./internal/awaitAbortable";
|
|
16
17
|
import { reportDiagnosticFailure } from "./internal/reportDiagnosticFailure";
|
|
17
18
|
|
|
18
19
|
export interface KernelTimings {
|
|
@@ -97,6 +98,9 @@ export class Kernel {
|
|
|
97
98
|
);
|
|
98
99
|
}
|
|
99
100
|
try {
|
|
101
|
+
// Async singletons first: hosted services and controllers take them
|
|
102
|
+
// through constructors, which resolve synchronously.
|
|
103
|
+
await awaitAbortable(this.container.initializeAsyncSingletons(), abortController.signal);
|
|
100
104
|
await this.coordinator.start(abortController.signal, this.options.shutdownTimeoutMs);
|
|
101
105
|
coordinatorStarted = true;
|
|
102
106
|
this.startupMs = performance.now() - this.timings.buildStartedAt;
|
|
@@ -156,6 +156,8 @@ interface CollectedDependency { readonly name: string; readonly target?: ts.Clas
|
|
|
156
156
|
interface ConstructorDependencies {
|
|
157
157
|
readonly deps: CollectedDependency[];
|
|
158
158
|
readonly unsupportedInheritedParameter?: string;
|
|
159
|
+
/** An own constructor parameter whose type cannot name a DI dependency (`string`, `number`, an inline type). */
|
|
160
|
+
readonly unsupportedParameter?: { readonly index: number; readonly name: string; readonly type: string };
|
|
159
161
|
}
|
|
160
162
|
let classDeps = new Map<ts.ClassDeclaration, readonly CollectedDependency[]>();
|
|
161
163
|
let inferredDiClasses = new Set<ts.ClassDeclaration>();
|
|
@@ -796,6 +798,10 @@ function collectClassDeps(source: ts.SourceFile): void {
|
|
|
796
798
|
const visit = (node: ts.Node): void => {
|
|
797
799
|
if (ts.isClassDeclaration(node) && node.name) {
|
|
798
800
|
const result = readConstructorTypeNames(node);
|
|
801
|
+
if (result.unsupportedParameter !== undefined && needsInferredDiDeps(node)) {
|
|
802
|
+
const { index, name, type } = result.unsupportedParameter;
|
|
803
|
+
fatalErrors.push(`BAZIS_DI_DEPENDENCY_UNKNOWN: ${sourceLocation(node)}: constructor parameter ${index + 1} "${name}" of "${node.name.text}" has type "${type}", which cannot be injected: DI resolves dependencies by class, contract or token, and a plain type does not say which value to inject. Declare a token with the same name as a type alias (export type Prefix = string; export const Prefix = createToken<Prefix>("Prefix")) and register a value, or pass the deps explicitly: scoped(${node.name.text}, ${node.name.text}, [TOKEN] as const).`);
|
|
804
|
+
}
|
|
799
805
|
if (result.unsupportedInheritedParameter !== undefined && needsInferredDiDeps(node)) {
|
|
800
806
|
fatalErrors.push(`BAZIS_DI_CONSTRUCTOR_UNRESOLVED: ${sourceLocation(node)}: cannot infer inherited constructor parameter "${result.unsupportedInheritedParameter}" for "${node.name.text}". The parameter needs a runtime DI token or an explicit value/factory binding.`);
|
|
801
807
|
}
|
|
@@ -984,7 +990,7 @@ function readConstructorTypeNames(node: ts.ClassDeclaration): ConstructorDepende
|
|
|
984
990
|
const paramType = param.type;
|
|
985
991
|
const refName = paramType ? getDependencyTypeName(paramType) : undefined;
|
|
986
992
|
if (!refName || BUILTIN_DEPENDENCY_NAMES.has(refName)) {
|
|
987
|
-
return { deps: [] };
|
|
993
|
+
return { deps: [], unsupportedParameter: { index, name: param.name.getText(), type: paramType?.getText() ?? "(no type)" } };
|
|
988
994
|
}
|
|
989
995
|
|
|
990
996
|
// `Lazy<X>` is a deferred dependency on X: encode it with a marker prefix
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { plugin } from "bun";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* bazis codegen uses the TypeScript compiler API (`createProgram`, the type
|
|
7
|
+
* checker). TypeScript 7 no longer ships it: `import ts from "typescript"`
|
|
8
|
+
* gives only the version. Microsoft publishes the TypeScript 6 API as
|
|
9
|
+
* `@typescript/typescript6`. When the `typescript` package seen from `fromDir`
|
|
10
|
+
* is 7 or newer, this makes `import ts from "typescript"` in the current
|
|
11
|
+
* process load that package instead. The project keeps TypeScript 7 as its
|
|
12
|
+
* compiler (`tsc`).
|
|
13
|
+
*
|
|
14
|
+
* Must run before anything imports "typescript": as a `--preload` of the
|
|
15
|
+
* codegen process, or in the CLI before its first dynamic import that needs it.
|
|
16
|
+
* Does nothing when `typescript` cannot be resolved (for example inside the
|
|
17
|
+
* compiled CLI binary, which bundles its own compiler API).
|
|
18
|
+
*/
|
|
19
|
+
export function useTypeScriptCompilerApi(fromDir: string): void {
|
|
20
|
+
let entry: string;
|
|
21
|
+
try {
|
|
22
|
+
entry = Bun.resolveSync("typescript", fromDir);
|
|
23
|
+
} catch {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const major = Number.parseInt(packageVersion(entry, "typescript")?.split(".")[0] ?? "", 10);
|
|
27
|
+
if (!Number.isFinite(major) || major < 7) return;
|
|
28
|
+
|
|
29
|
+
let api: string;
|
|
30
|
+
try {
|
|
31
|
+
api = Bun.resolveSync("@typescript/typescript6", fromDir);
|
|
32
|
+
} catch {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`BAZIS_TYPESCRIPT_API_MISSING: TypeScript ${major} has no compiler API, and bazis codegen needs one. ` +
|
|
35
|
+
"Install the TypeScript 6 API next to it: bun add -d @typescript/typescript6",
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const exact = new RegExp(`^${entry.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`);
|
|
39
|
+
plugin({
|
|
40
|
+
name: "bazis-typescript6-api",
|
|
41
|
+
setup(build) {
|
|
42
|
+
build.onLoad({ filter: exact }, () => ({
|
|
43
|
+
contents: `import ts from ${JSON.stringify(api)};\nexport default ts;\n`,
|
|
44
|
+
loader: "js",
|
|
45
|
+
}));
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Version from the package.json of the package that contains `file`. */
|
|
51
|
+
function packageVersion(file: string, name: string): string | undefined {
|
|
52
|
+
for (let dir = path.dirname(file); dir !== path.dirname(dir); dir = path.dirname(dir)) {
|
|
53
|
+
try {
|
|
54
|
+
const manifest = JSON.parse(readFileSync(path.join(dir, "package.json"), "utf8")) as { name?: string; version?: string };
|
|
55
|
+
if (manifest.name === name) return manifest.version;
|
|
56
|
+
} catch {
|
|
57
|
+
// No package.json at this level: keep walking up.
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Preloaded into the codegen process (`bun --preload`): see useTypeScriptCompilerApi.
|
|
2
|
+
import { useTypeScriptCompilerApi } from "./typescriptApi";
|
|
3
|
+
|
|
4
|
+
try {
|
|
5
|
+
useTypeScriptCompilerApi(import.meta.dir);
|
|
6
|
+
} catch (error) {
|
|
7
|
+
console.error(`[bazis] ${error instanceof Error ? error.message : String(error)}`);
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
@@ -653,8 +653,11 @@ export class OwnedStoreCatalogReader {
|
|
|
653
653
|
readonly OwnedCatalogArrayTypeV1[]
|
|
654
654
|
]> {
|
|
655
655
|
const relationOids = relations.filter(relation => relation.rowTypeOid !== null).map(relation => relation.oid);
|
|
656
|
-
if (!relationOids.length)
|
|
657
|
-
|
|
656
|
+
if (!relationOids.length) {
|
|
657
|
+
// An explicit tuple: TypeScript 7 infers Object.freeze([a, b]) as an array.
|
|
658
|
+
const empty: readonly [readonly OwnedCatalogRowTypeV1[], readonly OwnedCatalogArrayTypeV1[]] = [Object.freeze([]), Object.freeze([])];
|
|
659
|
+
return Object.freeze(empty);
|
|
660
|
+
}
|
|
658
661
|
const rowTypes: OwnedCatalogRowTypeV1[] = [], arrayTypes: OwnedCatalogArrayTypeV1[] = [];
|
|
659
662
|
for (let offset = 0; offset < relationOids.length; offset += 1024) {
|
|
660
663
|
const batch = relationOids.slice(offset, offset + 1024), parameter = this.encodePgOidArrayParameter(batch), issuedLimit = Math.min(batch.length + 1, budget.remaining + 1);
|
|
@@ -693,7 +696,8 @@ export class OwnedStoreCatalogReader {
|
|
|
693
696
|
this.source.drift();
|
|
694
697
|
}
|
|
695
698
|
}
|
|
696
|
-
|
|
699
|
+
const types: readonly [readonly OwnedCatalogRowTypeV1[], readonly OwnedCatalogArrayTypeV1[]] = [Object.freeze(rowTypes), Object.freeze(arrayTypes)];
|
|
700
|
+
return Object.freeze(types);
|
|
697
701
|
}
|
|
698
702
|
private async readColumns(relations: readonly OwnedCatalogRelationV1[], operation: "registry-read" | "catalog-read", budget: {
|
|
699
703
|
remaining: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bazis",
|
|
3
|
-
"version": "0.97.
|
|
3
|
+
"version": "0.97.5",
|
|
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",
|
|
@@ -71,10 +71,16 @@
|
|
|
71
71
|
"bun": ">=1.4.0"
|
|
72
72
|
},
|
|
73
73
|
"peerDependencies": {
|
|
74
|
-
"typescript": "^5.9.3 || ^6.0.0"
|
|
74
|
+
"typescript": "^5.9.3 || ^6.0.0 || ^7.0.0",
|
|
75
|
+
"@typescript/typescript6": "^6.0.0"
|
|
75
76
|
},
|
|
76
77
|
"publishConfig": {
|
|
77
78
|
"access": "public",
|
|
78
79
|
"provenance": true
|
|
80
|
+
},
|
|
81
|
+
"peerDependenciesMeta": {
|
|
82
|
+
"@typescript/typescript6": {
|
|
83
|
+
"optional": true
|
|
84
|
+
}
|
|
79
85
|
}
|
|
80
86
|
}
|