mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
73 lines
3.0 KiB
TypeScript
73 lines
3.0 KiB
TypeScript
import { mergeFlatPermissions } from "#src/permission-merge";
|
|
import type { RuleOrigin } from "#src/rule";
|
|
import type { FlatPermissionConfig, ScopeConfig } from "#src/types";
|
|
|
|
/** Surface → (pattern → originating scope). */
|
|
type OriginMap = Map<string, Map<string, RuleOrigin>>;
|
|
|
|
/** Result of merging permission objects across scopes with provenance tracking. */
|
|
export interface MergedScopes {
|
|
/** Fully merged flat permission config (lowest → highest precedence). */
|
|
mergedPermission: FlatPermissionConfig;
|
|
/** Maps each surface to a per-pattern origin (which scope contributed it). */
|
|
origins: OriginMap;
|
|
}
|
|
|
|
/**
|
|
* Merge permission objects across scopes (lowest → highest precedence) while
|
|
* tracking which scope contributed each (surface, pattern) entry.
|
|
*
|
|
* Mirrors mergeFlatPermissions() semantics for origin attribution:
|
|
* - Both base and incoming are objects → shallow-merge: each incoming pattern
|
|
* is attributed to this scope; patterns the higher scope does not redefine
|
|
* keep their earlier origin.
|
|
* - Otherwise → full replacement: this scope takes over the entire surface
|
|
* entry, discarding all lower-scope attribution.
|
|
*/
|
|
export function mergeScopesWithOrigins(
|
|
scopes: readonly (readonly [RuleOrigin, ScopeConfig])[],
|
|
): MergedScopes {
|
|
const origins: OriginMap = new Map();
|
|
let mergedPermission: FlatPermissionConfig = {};
|
|
|
|
for (const [scopeName, scope] of scopes) {
|
|
if (!scope.permission) continue;
|
|
|
|
for (const [surface, value] of Object.entries(scope.permission)) {
|
|
const baseVal = mergedPermission[surface];
|
|
/* eslint-disable @typescript-eslint/no-unnecessary-condition -- defensive null/type checks; config values may differ at runtime */
|
|
const bothObjects =
|
|
typeof baseVal === "object" &&
|
|
baseVal !== null &&
|
|
typeof value === "object" &&
|
|
value !== null;
|
|
/* eslint-enable @typescript-eslint/no-unnecessary-condition */
|
|
|
|
if (bothObjects) {
|
|
// Shallow-merge: each incoming pattern is attributed to this scope;
|
|
// existing patterns from lower scopes keep their earlier origin.
|
|
if (!origins.has(surface)) origins.set(surface, new Map());
|
|
for (const pattern of Object.keys(value)) {
|
|
origins.get(surface)?.set(pattern, scopeName);
|
|
}
|
|
} else {
|
|
// Full replacement: this scope takes over the entire surface entry.
|
|
const surfaceOrigins = new Map<string, RuleOrigin>();
|
|
if (typeof value === "string") {
|
|
surfaceOrigins.set("*", scopeName);
|
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- defensive null check
|
|
} else if (typeof value === "object" && value !== null) {
|
|
for (const pattern of Object.keys(value)) {
|
|
surfaceOrigins.set(pattern, scopeName);
|
|
}
|
|
}
|
|
origins.set(surface, surfaceOrigins);
|
|
}
|
|
}
|
|
|
|
mergedPermission = mergeFlatPermissions(mergedPermission, scope.permission);
|
|
}
|
|
|
|
return { mergedPermission, origins };
|
|
}
|