mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-06-13 18:31:16 +08:00
141 lines
5.4 KiB
JavaScript
141 lines
5.4 KiB
JavaScript
// ST-BME v3 GraphStore contract and pure router shell.
|
|
//
|
|
// Phase 6 only defines/validates the contract and route plans. Live adapters are
|
|
// ported in Phase 7 so durable routing is not switched accidentally.
|
|
|
|
export const GRAPH_STORE_CONTRACT_VERSION = 3;
|
|
|
|
export const GRAPH_STORE_KINDS = Object.freeze({
|
|
AUTHORITY: "authority",
|
|
OPFS: "opfs",
|
|
INDEXEDDB: "indexeddb",
|
|
LUKER_CHAT_STATE: "luker-chat-state",
|
|
NONE: "none",
|
|
});
|
|
|
|
export const GRAPH_STORE_REQUIRED_METHODS = Object.freeze([
|
|
"open",
|
|
"close",
|
|
"getMeta",
|
|
"patchMeta",
|
|
"commitDelta",
|
|
"exportSnapshot",
|
|
"exportSnapshotProbe",
|
|
"importSnapshot",
|
|
]);
|
|
|
|
export const GRAPH_STORE_OPTIONAL_METHODS = Object.freeze([
|
|
"readHead",
|
|
"writeHead",
|
|
"readCommitMarker",
|
|
"writeCommitMarker",
|
|
"isEmpty",
|
|
"deleteAll",
|
|
]);
|
|
|
|
function normalizeStoreKind(value = "") {
|
|
const kind = String(value || "").trim().toLowerCase();
|
|
if (Object.values(GRAPH_STORE_KINDS).includes(kind)) return kind;
|
|
return GRAPH_STORE_KINDS.NONE;
|
|
}
|
|
|
|
function methodExists(store = null, method = "") {
|
|
return store && typeof store[method] === "function";
|
|
}
|
|
|
|
export function inspectGraphStoreContract(store = null, options = {}) {
|
|
const requiredMethods = Array.isArray(options.requiredMethods)
|
|
? options.requiredMethods
|
|
: GRAPH_STORE_REQUIRED_METHODS;
|
|
const optionalMethods = Array.isArray(options.optionalMethods)
|
|
? options.optionalMethods
|
|
: GRAPH_STORE_OPTIONAL_METHODS;
|
|
const missingMethods = requiredMethods.filter((method) => !methodExists(store, method));
|
|
const supportedOptionalMethods = optionalMethods.filter((method) => methodExists(store, method));
|
|
return {
|
|
contractVersion: GRAPH_STORE_CONTRACT_VERSION,
|
|
valid: missingMethods.length === 0,
|
|
storeKind: normalizeStoreKind(store?.storeKind || store?.kind),
|
|
storeMode: String(store?.storeMode || store?.mode || ""),
|
|
missingMethods,
|
|
supportedOptionalMethods,
|
|
};
|
|
}
|
|
|
|
export function assertGraphStoreContract(store = null, options = {}) {
|
|
const inspection = inspectGraphStoreContract(store, options);
|
|
if (!inspection.valid) {
|
|
const error = new Error(`graph-store-contract-invalid:${inspection.missingMethods.join(",")}`);
|
|
error.code = "graph_store_contract_invalid";
|
|
error.contract = inspection;
|
|
throw error;
|
|
}
|
|
return inspection;
|
|
}
|
|
|
|
function normalizeBoolean(value) {
|
|
return value === true;
|
|
}
|
|
|
|
function normalizePreference(value = "") {
|
|
const normalized = String(value || "").trim().toLowerCase();
|
|
if (normalized === "authority-sql") return GRAPH_STORE_KINDS.AUTHORITY;
|
|
if (normalized === "opfs-primary" || normalized === "opfs-shadow") return GRAPH_STORE_KINDS.OPFS;
|
|
if (normalized === "indexeddb") return GRAPH_STORE_KINDS.INDEXEDDB;
|
|
if (normalized === "luker-chat-state") return GRAPH_STORE_KINDS.LUKER_CHAT_STATE;
|
|
return "auto";
|
|
}
|
|
|
|
function pushUniqueRoute(routes, kind, reason = "") {
|
|
const normalizedKind = normalizeStoreKind(kind);
|
|
if (!normalizedKind || normalizedKind === GRAPH_STORE_KINDS.NONE) return;
|
|
if (routes.some((route) => route.kind === normalizedKind)) return;
|
|
routes.push({ kind: normalizedKind, reason: String(reason || normalizedKind) });
|
|
}
|
|
|
|
export function planGraphStoreRoute(input = {}) {
|
|
const preference = normalizePreference(input.preference || input.primaryStorageTier || input.localStoreMode);
|
|
const capabilities = input.capabilities && typeof input.capabilities === "object" ? input.capabilities : {};
|
|
const environment = input.environment && typeof input.environment === "object" ? input.environment : {};
|
|
const hardCutNamespace = input.hardCutNamespace && typeof input.hardCutNamespace === "object"
|
|
? input.hardCutNamespace
|
|
: null;
|
|
const routes = [];
|
|
|
|
const authorityReady = normalizeBoolean(capabilities.authoritySqlReady || capabilities.storagePrimaryReady);
|
|
const opfsReady = normalizeBoolean(capabilities.opfsReady || capabilities.opfsAvailable);
|
|
const indexedDbReady =
|
|
normalizeBoolean(capabilities.indexedDbReady) || normalizeBoolean(capabilities.indexedDbAvailable);
|
|
const lukerReady = normalizeBoolean(environment.lukerChatStateReady || capabilities.lukerChatStateReady);
|
|
|
|
if (preference === GRAPH_STORE_KINDS.AUTHORITY && authorityReady) {
|
|
pushUniqueRoute(routes, GRAPH_STORE_KINDS.AUTHORITY, "preferred-authority-sql");
|
|
}
|
|
if (preference === GRAPH_STORE_KINDS.OPFS && opfsReady) {
|
|
pushUniqueRoute(routes, GRAPH_STORE_KINDS.OPFS, "preferred-opfs");
|
|
}
|
|
if (preference === GRAPH_STORE_KINDS.INDEXEDDB && indexedDbReady) {
|
|
pushUniqueRoute(routes, GRAPH_STORE_KINDS.INDEXEDDB, "preferred-indexeddb");
|
|
}
|
|
if (preference === GRAPH_STORE_KINDS.LUKER_CHAT_STATE && lukerReady) {
|
|
pushUniqueRoute(routes, GRAPH_STORE_KINDS.LUKER_CHAT_STATE, "preferred-luker-chat-state");
|
|
}
|
|
|
|
if (authorityReady) pushUniqueRoute(routes, GRAPH_STORE_KINDS.AUTHORITY, "authority-sql-ready");
|
|
if (opfsReady) pushUniqueRoute(routes, GRAPH_STORE_KINDS.OPFS, "opfs-ready");
|
|
if (indexedDbReady) pushUniqueRoute(routes, GRAPH_STORE_KINDS.INDEXEDDB, "indexeddb-ready");
|
|
if (lukerReady) pushUniqueRoute(routes, GRAPH_STORE_KINDS.LUKER_CHAT_STATE, "luker-chat-state-ready");
|
|
|
|
return {
|
|
contractVersion: GRAPH_STORE_CONTRACT_VERSION,
|
|
hardCut: true,
|
|
hotPathReadsLegacy: false,
|
|
namespace: hardCutNamespace,
|
|
primary: routes[0]?.kind || GRAPH_STORE_KINDS.NONE,
|
|
fallback: routes.slice(1).map((route) => route.kind),
|
|
routes,
|
|
blocked: routes.length === 0,
|
|
reason: routes.length ? routes[0].reason : "no-graph-store-route-ready",
|
|
};
|
|
}
|