Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | 1x 112x 59x 59x 167x 167x 66x 66x 66x 2x 34x 27x 96x 199x 199x 74x 125x 56x 8x 20x 19x 19x 12x 12x 12x 8x 56x 28x 8x 8x 8x 16x 21x 8x 22x 8x 22x 7x | import type {
DeviceContext,
ProfileOverrides,
ResolvedProfile,
SmartCubeProfile,
SmartCubeProfilePatch,
} from './types.js';
import { mergeProfiles } from './mergeProfiles.js';
const keys = ['protocol', 'deviceName', 'deviceMAC', 'hardwareName', 'goCubeType'] as const;
function score(profile: SmartCubeProfile, context: DeviceContext): number {
if (!profile.match) return profile.id === 'base' ? 0 : 1;
let result = 0;
for (const key of keys) {
const expected = profile.match[key];
if (!expected) continue;
const actual = context[key];
try {
if (!actual || !new RegExp(`^(?:${expected})$`, 'i').test(actual)) return -1;
} catch {
return -1;
}
result += 10;
}
return result;
}
function recordLeafSources(
value: object,
source: string,
sources: Record<string, string>,
prefix = '',
): void {
for (const [key, child] of Object.entries(value) as Array<[string, unknown]>) {
const path = prefix ? `${prefix}.${key}` : key;
if (child && typeof child === 'object' && !Array.isArray(child)) {
recordLeafSources(child, source, sources, path);
} else {
sources[path] = source;
}
}
}
/** Select and resolve the most specific profile for a cube identity. */
export function resolveProfile(
context: DeviceContext,
profiles: readonly SmartCubeProfile[],
overrides: ProfileOverrides = {},
): ResolvedProfile {
const byId = new Map(profiles.map((profile) => [profile.id, profile]));
const resolveChain = (
profile: SmartCubeProfile,
visiting = new Set<string>(),
): SmartCubeProfile[] => {
if (visiting.has(profile.id)) throw new Error(`Profile inheritance cycle at '${profile.id}'`);
const nextVisiting = new Set(visiting).add(profile.id);
if (!profile.extends) return [profile];
const parent = byId.get(profile.extends);
Iif (!parent)
throw new Error(`Profile '${profile.id}' extends missing profile '${profile.extends}'`);
return [...resolveChain(parent, nextVisiting), profile];
};
const matches = profiles
.filter((profile) => score(profile, context) >= 0)
.sort((a, b) => score(a, context) - score(b, context));
const selected = matches.at(-1) ?? byId.get('unknown')!;
const chain = resolveChain(selected);
const layers: Array<[string, SmartCubeProfile | SmartCubeProfilePatch]> = [
...chain.map((profile) => [profile.id, profile] as [string, SmartCubeProfile]),
...(['app', 'user', 'runtime'] as const).flatMap((layer) =>
overrides[layer] ? [[layer, overrides[layer]] as [string, SmartCubeProfilePatch]] : [],
),
];
const value = layers.reduce(
(merged, [, layer]) => mergeProfiles(merged, layer),
{} as SmartCubeProfile,
);
const sources: Record<string, string> = {};
for (const [source, layer] of layers) recordLeafSources(layer, source, sources);
return { id: selected.id, value, sources };
}
|