bazis 0.139.0 → 0.140.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.
@@ -16,8 +16,17 @@ import type { PermissionsOf } from "./catalog/permissions";
16
16
  */
17
17
  export interface BazisPermissions extends PermissionsOf<typeof accessPermissions> {}
18
18
 
19
+ /**
20
+ * The prefixes of the application's permission sources (ACCESS-001 §3.1): any
21
+ * key under one of them is accepted by `can`, `access.can` and `access.filter`.
22
+ * Nobody writes it by hand: `bazis codegen` fills it from `permissionSources`
23
+ * of the application's modules.
24
+ */
25
+ export interface BazisPermissionSources {}
26
+
19
27
  export { AccessModule } from "./Access.module";
20
- export { definePermissions, type AttributeSpec, type OwnPermissionKey, type PermissionDeclarations, type PermissionKey, type PermissionsOf, type PermissionSpec, type ScopeSource } from "./catalog/permissions";
28
+ export { definePermissions, type AnyPermissionKey, type AttributeSpec, type OwnPermissionKey, type PermissionDeclarations, type PermissionKey, type PermissionsOf, type PermissionSpec, type ScopeSource, type SourcePermissionKey } from "./catalog/permissions";
29
+ export type { PermissionSourceState } from "./catalog/PermissionSources";
21
30
  export { IAccessCatalog } from "./catalog/IAccessCatalog.service";
22
31
  export type { DeclaredRole, OwnedPermission, OwnedReferenceKind } from "./catalog/AccessCatalog";
23
32
  export { IAccess } from "./check/IAccess.service";
@@ -27,12 +36,14 @@ export { can } from "./check/can";
27
36
  export { grantsToWhere, type AccessFilter, type ColumnCondition } from "./check/filter";
28
37
  export { AccessPolicy, type ScopePrincipals } from "./contracts/AccessPolicy";
29
38
  export { AttributeLookup, type LookupColumn } from "./contracts/AttributeLookup";
39
+ export { PermissionSource } from "./contracts/PermissionSource";
30
40
  export { References, type ReferenceContext, type ReferenceItem, type ReferencePage } from "./contracts/References";
31
41
  export { AccessFilterTooLargeError, AccessSetupError } from "./contracts/errors";
32
42
  export type {
33
43
  AccessObject,
34
44
  AccessSubject,
35
45
  AccessValue,
46
+ CompareTest,
36
47
  Condition,
37
48
  Grant,
38
49
  GrantFields,
@@ -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.139.0" }, capabilities: { experimentalApi: true },
235
+ clientInfo: { name: "bazis", title: "Bazis", version: "0.140.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");
@@ -191,6 +191,8 @@ interface ConstructorDependencies {
191
191
  let classDeps = new Map<ts.ClassDeclaration, readonly CollectedDependency[]>();
192
192
  /** `permissions` of the target's modules: the exported constant and its file. */
193
193
  let permissionDeclarations = new Map<string, { readonly name: string; readonly file: string }>();
194
+ /** The prefixes of `permissionSources` of the target's modules: any key under one is a permission key. */
195
+ let permissionSourcePrefixes = new Set<string>();
194
196
  let inferredDiClasses = new Set<ts.ClassDeclaration>();
195
197
  let explicitlyBoundDiClasses = new Set<ts.ClassDeclaration>();
196
198
  // Known runtime token names: createToken descriptions + class names usable as class-tokens.
@@ -507,7 +509,7 @@ function collectDynamicImportDiagnostics(source: ts.SourceFile, target: string,
507
509
 
508
510
  function resetTargetAnalysis(files: Set<string>, generatedDir: string): void {
509
511
  activeGeneratedDir = generatedDir;
510
- classDeps = new Map(); inferredDiClasses = new Set(); explicitlyBoundDiClasses = new Set(); permissionDeclarations = new Map();
512
+ classDeps = new Map(); inferredDiClasses = new Set(); explicitlyBoundDiClasses = new Set(); permissionDeclarations = new Map(); permissionSourcePrefixes = new Set();
511
513
  tokenDescriptions = new Set(); configPrefixesByType = new Map(); knownClassNames = new Set(); warnings = []; fatalErrors = [];
512
514
  httpBindings = {}; httpControllerFiles = {}; openApiSchemas = {}; openApiOperations = {};
513
515
  grpcBindings = new Map();
@@ -557,7 +559,7 @@ async function generateTarget(name: string, reachable: Set<string>, production:
557
559
  stageWrite(`${generatedDir}/httpRequestModels.ts`, requestModels);
558
560
  stageWrite(`${generatedDir}/httpListModels.ts`, listModels);
559
561
  stageWrite(`${generatedDir}/openapi.ts`, openApi);
560
- if (permissionDeclarations.size > 0) stageWrite(`${generatedDir}/permissions.ts`, renderPermissions());
562
+ if (permissionDeclarations.size > 0 || permissionSourcePrefixes.size > 0) stageWrite(`${generatedDir}/permissions.ts`, renderPermissions());
561
563
  else stageRemove(`${generatedDir}/permissions.ts`);
562
564
  // Generated before 0.99.0, when agents were classes; nothing imports it now.
563
565
  stageRemove(`${generatedDir}/agentCatalog.ts`);
@@ -715,9 +717,17 @@ function renderGeneratedRuntime(targetId: string): string {
715
717
  /** Types only: the keys of the target's permissions for `can`, `access.can`, `access.filter` and `defineRoles`. */
716
718
  function renderPermissions(): string {
717
719
  const entries = [...permissionDeclarations.values()].sort((left, right) => left.file.localeCompare(right.file) || left.name.localeCompare(right.name));
718
- const lines = ["// This file is auto-generated by `bazis codegen`.", "// Do not edit manually.", 'import type { PermissionsOf } from "bazis/core/access";'];
720
+ const lines = ["// This file is auto-generated by `bazis codegen`.", "// Do not edit manually."];
721
+ // Without an import or an export the file would be a script, and `declare module` would replace the module instead of adding to it.
722
+ lines.push(entries.length > 0 ? 'import type { PermissionsOf } from "bazis/core/access";' : "export {};");
719
723
  entries.forEach((entry, index) => lines.push(`import type { ${entry.name} as Permissions_${index} } from ${JSON.stringify(toModuleSpecifierFrom(activeGeneratedDir, entry.file))};`));
720
- lines.push("", 'declare module "bazis/core/access" {', ` interface BazisPermissions extends ${entries.map((_, index) => `PermissionsOf<typeof Permissions_${index}>`).join(", ")} {}`, "}", "");
724
+ lines.push("", 'declare module "bazis/core/access" {');
725
+ if (entries.length > 0) lines.push(` interface BazisPermissions extends ${entries.map((_, index) => `PermissionsOf<typeof Permissions_${index}>`).join(", ")} {}`);
726
+ // The prefixes of `permissionSources`: `${prefix}.${string}` is a permission key too (ACCESS-001 §3.1).
727
+ if (permissionSourcePrefixes.size > 0) {
728
+ lines.push(" interface BazisPermissionSources {", ...[...permissionSourcePrefixes].sort().map((prefix) => ` ${JSON.stringify(prefix)}: true;`), " }");
729
+ }
730
+ lines.push("}", "");
721
731
  return lines.join("\n");
722
732
  }
723
733
 
@@ -1036,6 +1046,9 @@ function collectDiClassRegistrations(source: ts.SourceFile): void {
1036
1046
  // `references: { "orders.order": OrderReferences }`: the field registers the classes.
1037
1047
  const references = objectFor(properties.get("references"));
1038
1048
  if (references) for (const entry of propertiesOf(references).values()) record(entry);
1049
+ // `permissionSources: { data: DataPermissionSource }`: the field registers the classes too.
1050
+ const permissionSources = objectFor(properties.get("permissionSources"));
1051
+ if (permissionSources) for (const entry of propertiesOf(permissionSources).values()) record(entry);
1039
1052
  // `permissions: orderPermissions`: the field registers the `lookup` classes its permissions name.
1040
1053
  for (const lookup of permissionLookups(properties.get("permissions"))) record(lookup);
1041
1054
  const imports = properties.get("imports");
@@ -1177,6 +1190,22 @@ function collectProviderFormErrors(source: ts.SourceFile): void {
1177
1190
  }
1178
1191
  permissionDeclarations.set(`${file}\0${exported.name}`, { name: exported.name, file });
1179
1192
  };
1193
+ /**
1194
+ * `permissionSources: { data: DataPermissionSource }`: the prefixes go to the
1195
+ * generated `permissions.ts`, which makes every key under them a permission
1196
+ * key for `can` and `access.filter`. A framework pack names its own in its sources.
1197
+ */
1198
+ const collectPermissionSources = (module: string, initializer: ts.Expression): void => {
1199
+ const file = normalizeProjectPath(initializer.getSourceFile().fileName);
1200
+ if (file.includes("/node_modules/") || isFrameworkPackPath(file)) return;
1201
+ const object = resolved(initializer, ts.isObjectLiteralExpression);
1202
+ const named = object?.properties.every((property) => ts.isPropertyAssignment(property) && (ts.isIdentifier(property.name) || ts.isStringLiteralLike(property.name)));
1203
+ if (!object || named !== true) {
1204
+ fatalErrors.push(`BAZIS_ACCESS_SOURCES_FORM: ${sourceLocation(initializer)}: permissionSources of ${module}: "${text(initializer)}" — write an object "prefix: Class" in the module, so that the keys under the prefixes become the type of can(...).`);
1205
+ return;
1206
+ }
1207
+ for (const property of object.properties) permissionSourcePrefixes.add((property.name as ts.Identifier | ts.StringLiteralLike).text);
1208
+ };
1180
1209
  const checkModule = (module: string, argument: ts.Expression | undefined): void => {
1181
1210
  const object = argument && resolved(argument, ts.isObjectLiteralExpression);
1182
1211
  if (!object) return;
@@ -1184,6 +1213,7 @@ function collectProviderFormErrors(source: ts.SourceFile): void {
1184
1213
  if (!ts.isPropertyAssignment(property)) continue;
1185
1214
  const field = property.name.getText().replace(/["']/g, "");
1186
1215
  if (field === "permissions") collectPermissions(module, property.initializer);
1216
+ if (field === "permissionSources") collectPermissionSources(module, property.initializer);
1187
1217
  if (field !== "providers" && !CLASS_LISTS.includes(field)) continue;
1188
1218
  const list = resolved(property.initializer, ts.isArrayLiteralExpression);
1189
1219
  if (!list) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bazis",
3
- "version": "0.139.0",
3
+ "version": "0.140.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",