bazis 0.98.13 → 0.98.15

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.
@@ -110,7 +110,9 @@ export class GraphValidator {
110
110
 
111
111
  if (this.context.validateScopes && rootLifetime === "singleton" && depRegistration.lifetime === "scoped") {
112
112
  issues.add(
113
- `Singleton "${tokenToDebugName(registration.token)}" depends on scoped "${tokenToDebugName(descriptor.token)}"`,
113
+ `Singleton "${tokenToDebugName(registration.token)}" depends on scoped "${tokenToDebugName(descriptor.token)}". `
114
+ + `A scoped service lives for one request or scope: make "${tokenToDebugName(registration.token)}" scoped too, `
115
+ + `or inject ServiceProvider and resolve "${tokenToDebugName(descriptor.token)}" in a scope you create (provider.createScope()).`,
114
116
  );
115
117
  }
116
118
 
@@ -173,6 +173,14 @@ export function validateModuleEncapsulation(
173
173
  // Why a provided token is not visible: the owner does not export it, or the
174
174
  // consumer module does not import the owner. Both used to read "not exported".
175
175
  const explainHidden = (token: Token<unknown>, consumer: ModuleGraphRecord): string => {
176
+ // A module the consumer imports already sees the token but does not pass it
177
+ // on. That export is the fix, whatever inner module (an `ormBazis` feature
178
+ // module, say) registered the token.
179
+ const blocking = consumer.imports.find((imported) => !imported.providedTokens.has(token)
180
+ && !exportedTokens(imported).has(token) && visibleTokens(imported).has(token));
181
+ if (blocking !== undefined) {
182
+ return `which its import "${blocking.name}" receives from its own imports but does not export. Add it to the exports of "${blocking.name}".`;
183
+ }
176
184
  const owner = records.find((record) => record !== consumer && record.providedTokens.has(token));
177
185
  if (owner === undefined) return "which is provided by another module but not exported.";
178
186
  return exportedTokens(owner).has(token)
@@ -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.98.13" }, capabilities: { experimentalApi: true },
235
+ clientInfo: { name: "bazis", title: "Bazis", version: "0.98.15" }, 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");
@@ -38,8 +38,10 @@ export class ModelBuilder {
38
38
  const indexes: IndexModel[] = [];
39
39
  const foreignKeys: ForeignKeyModel[] = [];
40
40
  let explicitKey: PropertyModel | undefined;
41
+ const sample = ModelBuilder.sampleOf(ctor);
41
42
 
42
43
  for (const rawProp of raw.properties.values()) {
44
+ ModelBuilder.assertTypeMatchesInitializer(ctor.name, rawProp, sample);
43
45
  const property = ModelBuilder.buildProperty(ctor.name, rawProp);
44
46
  properties.push(property);
45
47
  if (property.isKey) {
@@ -184,6 +186,43 @@ export class ModelBuilder {
184
186
  return model;
185
187
  }
186
188
 
189
+ /** A fresh instance shows the initial values; an entity whose constructor throws skips the check. */
190
+ private static sampleOf(ctor: new () => object): Record<string, unknown> | undefined {
191
+ try {
192
+ return new ctor() as Record<string, unknown>;
193
+ } catch {
194
+ return undefined;
195
+ }
196
+ }
197
+
198
+ /**
199
+ * A column without an explicit type maps to `text` (a key to an integer
200
+ * identity). When the initial value says otherwise (`pages = 0`,
201
+ * `code = ""` on a key), the mapping would silently change the value's type
202
+ * or drop it, so the model refuses to build and names the type to set.
203
+ */
204
+ private static assertTypeMatchesInitializer(entityName: string, raw: RawProperty, sample: Record<string, unknown> | undefined): void {
205
+ if (sample === undefined || raw.type !== undefined || raw.convention !== undefined || raw.converter !== undefined) return;
206
+ const initial = sample[raw.propertyName];
207
+ if (initial === undefined || initial === null) return;
208
+ const kind = initial instanceof Date ? "Date" : Array.isArray(initial) ? "array" : typeof initial;
209
+ if (raw.isKey === true) {
210
+ if (kind === "number" || kind === "bigint") return;
211
+ throw new ModelBuildError(
212
+ `Entity "${entityName}": key "${raw.propertyName}" has no column type and maps to an integer identity, but its initial value is a ${kind}. `
213
+ + `For a text key add @Column({ type: "text" }) next to @Key(); for a UUID key use @UUID().`,
214
+ );
215
+ }
216
+ if (kind === "string") return;
217
+ const suggestion = kind === "number" || kind === "bigint" ? `@Column({ type: "integer" }) or @Column({ type: "real" })`
218
+ : kind === "boolean" ? `@Column({ type: "boolean" })`
219
+ : kind === "Date" ? `@Column({ type: "datetime" })`
220
+ : `@Column({ type: "json" })`;
221
+ throw new ModelBuildError(
222
+ `Entity "${entityName}": property "${raw.propertyName}" has no column type and maps to text, but its initial value is a ${kind}. Set the type: ${suggestion}.`,
223
+ );
224
+ }
225
+
187
226
  private static buildProperty(entityName: string, raw: RawProperty): PropertyModel {
188
227
  const isKey = raw.isKey === true;
189
228
  const resolved = resolveColumnType(raw.type, isKey);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bazis",
3
- "version": "0.98.13",
3
+ "version": "0.98.15",
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",