bazis 0.98.21 → 0.98.22

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.
@@ -1,7 +1,18 @@
1
1
  import { DiError } from "./DiError";
2
2
 
3
+ /** Returns a hint for a named dependency nobody registers, or `undefined`. */
4
+ export type NamedDependencyHint = (name: string) => string | undefined;
5
+
6
+ const hints: NamedDependencyHint[] = [];
7
+
8
+ /** Infrastructure extensions (ORM repositories, …) explain their own named dependencies here. */
9
+ export function registerNamedDependencyHint(hint: NamedDependencyHint): void {
10
+ hints.push(hint);
11
+ }
12
+
3
13
  export class NamedDependencyNotFoundError extends DiError {
4
14
  public constructor(name: string) {
5
- super(`No provider token found for named dependency "${name}".`);
15
+ const hint = hints.map((item) => item(name)).find((text) => text !== undefined);
16
+ super(`No provider token found for named dependency "${name}".${hint ? ` ${hint}` : ""}`);
6
17
  }
7
18
  }
@@ -84,7 +84,9 @@ export function validateModuleEncapsulation(
84
84
  if (isFamilyExport(ref)) {
85
85
  continue;
86
86
  }
87
- if (record.providedTokens.has(ref) || visible.has(ref)) {
87
+ // A closed token of a visible family (`repositoryFor(Product)` next to an
88
+ // `ormBazis` that exports the whole `IRepository` family) may be passed on alone.
89
+ if (record.providedTokens.has(ref) || visible.has(ref) || (typeof ref !== "function" && ref.genericFamilyId !== undefined && allProvidedTokens.has(ref) && visibleFamilies(record).has(ref.genericFamilyId))) {
88
90
  result.add(ref);
89
91
  } else {
90
92
  issues.push(
@@ -173,6 +175,10 @@ export function validateModuleEncapsulation(
173
175
  // Why a provided token is not visible: the owner does not export it, or the
174
176
  // consumer module does not import the owner. Both used to read "not exported".
175
177
  const explainHidden = (token: Token<unknown>, consumer: ModuleGraphRecord): string => {
178
+ if (typeof token !== "function" && token.genericFamilyId !== undefined) {
179
+ const family = explainHiddenFamily(token, token.genericFamilyId, consumer);
180
+ if (family !== undefined) return family;
181
+ }
176
182
  // A module the consumer imports already sees the token but does not pass it
177
183
  // on. That export is the fix, whatever inner module (an `ormBazis` feature
178
184
  // module, say) registered the token.
@@ -188,6 +194,31 @@ export function validateModuleEncapsulation(
188
194
  : `which module "${owner.name}" provides but does not export. Add it to the exports of "${owner.name}".`;
189
195
  };
190
196
 
197
+ // A closed token (`IRepository<Product>`) travels with its family export, so
198
+ // the fix is named in family terms and skips unnamed inner modules such as
199
+ // the one `ormBazis` creates.
200
+ const explainHiddenFamily = (token: Token<unknown>, familyId: symbol, consumer: ModuleGraphRecord): string | undefined => {
201
+ const closed = tokenToDebugName(token);
202
+ const family = closed.includes("<") ? closed.slice(0, closed.indexOf("<")) : closed;
203
+ const exportFix = `${family} (or only ${closed})`;
204
+ const sees = (record: ModuleGraphRecord) => visibleFamilies(record).has(familyId) || visibleTokens(record).has(token);
205
+ const passes = (record: ModuleGraphRecord) => exportedFamilies(record).has(familyId) || exportedTokens(record).has(token);
206
+ const named = (record: ModuleGraphRecord) => !/^module#\d+$/.test(record.name);
207
+ const blocking = consumer.imports.find((imported) => named(imported) && sees(imported) && !passes(imported));
208
+ if (blocking !== undefined) {
209
+ return `which its import "${blocking.name}" receives from its own imports but does not export. Add ${exportFix} to the exports of "${blocking.name}".`;
210
+ }
211
+ const exporter = records.find((record) => record !== consumer && named(record) && passes(record));
212
+ if (exporter !== undefined) {
213
+ return `which module "${exporter.name}" exports, but "${consumer.name}" does not list "${exporter.name}" in its imports. Add "${exporter.name}" to the imports of "${consumer.name}".`;
214
+ }
215
+ const host = records.find((record) => record !== consumer && named(record) && sees(record));
216
+ if (host !== undefined) {
217
+ return `which module "${host.name}" receives from its own imports but does not export. Add ${exportFix} to the exports of "${host.name}" and "${host.name}" to the imports of "${consumer.name}".`;
218
+ }
219
+ return undefined;
220
+ };
221
+
191
222
  const contexts = new Map<ModuleGraphRecord, DependencyCheckContext>();
192
223
  for (let recordIndex = 0; recordIndex < records.length; recordIndex += 1) {
193
224
  const record = records[recordIndex] as ModuleGraphRecord;
@@ -302,7 +333,7 @@ function checkDependency(
302
333
  !context.familiesVisible.has(token.genericFamilyId)
303
334
  ) {
304
335
  context.issues.push(
305
- `Module "${record.name}": "${consumer}" depends on open generic "${tokenToDebugName(token)}", whose family is not exported by any imported module.`,
336
+ `Module "${record.name}": "${consumer}" depends on "${tokenToDebugName(token)}", ${context.explainHidden(token, record)}`,
306
337
  );
307
338
  }
308
339
  return;
@@ -363,6 +394,7 @@ export function computeModuleVisibleTokens(
363
394
  ): Map<ModuleGraphRecord, Set<Token<unknown>>> {
364
395
  const exportedMemo = new Map<ModuleGraphRecord, Set<Token<unknown>>>();
365
396
  const visiting = new Set<ModuleGraphRecord>();
397
+ const allProvided = new Set<Token<unknown>>(records.flatMap((record) => [...record.providedTokens]));
366
398
 
367
399
  const visibleOf = (record: ModuleGraphRecord): Set<Token<unknown>> => {
368
400
  const result = new Set<Token<unknown>>(record.providedTokens);
@@ -401,7 +433,8 @@ export function computeModuleVisibleTokens(
401
433
  if (isFamilyExport(ref)) {
402
434
  continue;
403
435
  }
404
- if (record.providedTokens.has(ref) || visible.has(ref)) {
436
+ // Validation already admitted closed tokens re-exported from a visible family.
437
+ if (record.providedTokens.has(ref) || visible.has(ref) || (typeof ref !== "function" && ref.genericFamilyId !== undefined && allProvided.has(ref))) {
405
438
  result.add(ref);
406
439
  }
407
440
  }
@@ -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.21" }, capabilities: { experimentalApi: true },
235
+ clientInfo: { name: "bazis", title: "Bazis", version: "0.98.22" }, 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");
@@ -1,9 +1,13 @@
1
1
  import { registerNamedDependencyEncapsulationHook } from "../di/module/encapsulationHooks";
2
+ import { registerNamedDependencyHint } from "../di/errors/NamedDependencyNotFoundError";
2
3
  import { IRepository } from "./repository";
3
4
 
4
5
  let registered = false;
5
6
 
6
- /** Allows closed `IRepository<Entity>` named deps when the module exports open generic `IRepository`. */
7
+ /**
8
+ * Allows closed `IRepository<Entity>` named deps when the module exports open
9
+ * generic `IRepository`, and explains a repository nobody registers.
10
+ */
7
11
  export function registerRepositoryEncapsulationHook(): void {
8
12
  if (registered) {
9
13
  return;
@@ -16,4 +20,10 @@ export function registerRepositoryEncapsulationHook(): void {
16
20
  }
17
21
  return undefined;
18
22
  });
23
+
24
+ registerNamedDependencyHint((name) => {
25
+ const match = /^IRepository<(.+)>$/.exec(name);
26
+ if (!match) return undefined;
27
+ return `Repositories are registered by ormBazis for the entities of its context: add ${match[1]} to the entities of an ormBazis module and keep registerRepositories on (the default), or inject that module's DbContext instead.`;
28
+ });
19
29
  }
@@ -82,6 +82,9 @@ const DataModule = ormModule({
82
82
  ```
83
83
 
84
84
  `ormModule` exports the `IRepository` family token so importing modules can inject `repositoryFor(User)`.
85
+ A feature module that declares `ormBazis` passes repositories on with its own
86
+ `exports`: `exports: [IRepository]` opens the repositories of all its entities,
87
+ `exports: [repositoryFor(User)]` (since 0.98.22) only that one.
85
88
 
86
89
  Turning repositories off:
87
90
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bazis",
3
- "version": "0.98.21",
3
+ "version": "0.98.22",
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",