fiemwl / src /lib /sync /bundle.ts
automindy's picture
Upload 1980 files
6111b2b verified
Raw
History Blame Contribute Delete
5.61 kB
import { createHash } from "crypto";
import {
getApiKeys,
getCombos,
getModelAliases,
getProviderConnections,
getProviderNodes,
getSettings,
} from "@/lib/localDb";
type JsonRecord = Record<string, unknown>;
export interface ConfigSyncBundle {
settings: JsonRecord;
providerConnections: JsonRecord[];
providerNodes: JsonRecord[];
modelAliases: JsonRecord;
combos: JsonRecord[];
apiKeys: JsonRecord[];
}
function asRecord(value: unknown): JsonRecord {
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
}
function sanitizeSettingsForSync(settings: unknown): JsonRecord {
const record = asRecord(settings);
const {
password: _password,
requireLogin: _requireLogin,
cloudEnabled: _cloudEnabled,
...safeSettings
} = record;
return safeSettings;
}
function sortByStringKeys<T extends JsonRecord>(items: T[], keys: string[]) {
return [...items].sort((a, b) => {
for (const key of keys) {
const leftRaw = a[key];
const rightRaw = b[key];
if (typeof leftRaw === "number" || typeof rightRaw === "number") {
const left = typeof leftRaw === "number" ? leftRaw : Number.MAX_SAFE_INTEGER;
const right = typeof rightRaw === "number" ? rightRaw : Number.MAX_SAFE_INTEGER;
if (left !== right) return left - right;
continue;
}
const left = typeof leftRaw === "string" ? String(leftRaw) : "";
const right = typeof rightRaw === "string" ? String(rightRaw) : "";
const comparison = left.localeCompare(right, undefined, { numeric: true });
if (comparison !== 0) return comparison;
}
return 0;
});
}
function pickDefined(record: JsonRecord, keys: string[]) {
return Object.fromEntries(
keys.filter((key) => record[key] !== undefined).map((key) => [key, record[key]])
);
}
function sanitizeProviderConnectionForSync(connection: unknown): JsonRecord {
const record = asRecord(connection);
return pickDefined(record, [
"id",
"provider",
"authType",
"name",
"displayName",
"email",
"priority",
"globalPriority",
"defaultModel",
"isActive",
"accessToken",
"refreshToken",
"expiresAt",
"expiresIn",
"tokenType",
"scope",
"idToken",
"projectId",
"apiKey",
"providerSpecificData",
"group",
]);
}
function sanitizeProviderNodeForSync(node: unknown): JsonRecord {
const record = asRecord(node);
return pickDefined(record, [
"id",
"type",
"name",
"prefix",
"apiType",
"baseUrl",
"chatPath",
"modelsPath",
]);
}
function sanitizeComboForSync(combo: unknown): JsonRecord {
const record = asRecord(combo);
const { createdAt: _createdAt, updatedAt: _updatedAt, ...rest } = record;
return rest;
}
function sanitizeApiKeyForSync(apiKey: unknown): JsonRecord {
const record = asRecord(apiKey);
return pickDefined(record, [
"id",
"name",
"key",
"machineId",
"allowedModels",
"allowedCombos",
"allowedConnections",
"noLog",
"autoResolve",
"isActive",
"accessSchedule",
"maxRequestsPerDay",
"maxRequestsPerMinute",
"throttleDelayMs",
"maxSessions",
]);
}
function canonicalizeJson(value: unknown): unknown {
if (Array.isArray(value)) {
return value.map((entry) => canonicalizeJson(entry));
}
if (value && typeof value === "object") {
return Object.fromEntries(
Object.keys(value as JsonRecord)
.sort((a, b) => a.localeCompare(b))
.map((key) => [key, canonicalizeJson((value as JsonRecord)[key])])
);
}
return value;
}
export function serializeStableJson(value: unknown) {
return JSON.stringify(canonicalizeJson(value));
}
export function computeConfigSyncVersion(bundle: ConfigSyncBundle) {
return createHash("sha256").update(serializeStableJson(bundle)).digest("hex");
}
export async function buildConfigSyncBundle(): Promise<ConfigSyncBundle> {
const [settings, providerConnections, providerNodes, modelAliases, combos, apiKeys] =
await Promise.all([
getSettings(),
getProviderConnections(),
getProviderNodes(),
getModelAliases(),
getCombos(),
getApiKeys(),
]);
return {
settings: sanitizeSettingsForSync(settings),
providerConnections: sortByStringKeys(
providerConnections.map((connection) => sanitizeProviderConnectionForSync(connection)),
["provider", "name", "id"]
),
providerNodes: sortByStringKeys(
providerNodes.map((node) => sanitizeProviderNodeForSync(node)),
["type", "name", "id"]
),
modelAliases: asRecord(modelAliases),
combos: sortByStringKeys(
combos.map((combo) => sanitizeComboForSync(combo)),
["sortOrder", "name", "id"]
),
apiKeys: sortByStringKeys(
apiKeys.map((apiKey) => sanitizeApiKeyForSync(apiKey)),
["name", "id"]
),
};
}
export async function buildConfigSyncEnvelope() {
const bundle = await buildConfigSyncBundle();
const version = computeConfigSyncVersion(bundle);
return {
version,
bundle,
};
}
export function toLegacyCloudSyncPayload(bundle: ConfigSyncBundle) {
return {
providers: bundle.providerConnections,
providerNodes: bundle.providerNodes,
modelAliases: bundle.modelAliases,
combos: bundle.combos,
apiKeys: bundle.apiKeys,
settings: bundle.settings,
};
}