release: v0.5.1 — 存储后端生产级硬化(CRC-32/全库加密/WAL分片/页面化存储/多标签页锁/e2e)+ 深度审查修复(假实现接线/死代码清理)
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 测试共享 — 真实语义 OPFS mock
|
||||
*
|
||||
* 模拟 OPFS 的完整语义:
|
||||
* - createWritable({ keepExistingData }) + position 追加写
|
||||
* - 文件内容以 ArrayBuffer 存储(跨 backend 实例共享于同一 Map)
|
||||
* - entries() 迭代、removeEntry 删除
|
||||
*
|
||||
* 用法:
|
||||
* const files = installOPFSMock(new Map());
|
||||
* const backend = new OPFSBackend();
|
||||
* await backend.open('any');
|
||||
*/
|
||||
|
||||
export interface MockOpfsFile {
|
||||
content: ArrayBuffer;
|
||||
}
|
||||
|
||||
export function installOPFSMock(files: Map<string, MockOpfsFile>): {
|
||||
files: Map<string, MockOpfsFile>;
|
||||
dir: {
|
||||
getFileHandle: (name: string, opts?: { create?: boolean }) => Promise<{
|
||||
getFile: () => Promise<{ size: number; arrayBuffer: () => Promise<ArrayBuffer> }>;
|
||||
createWritable: (wOpts?: { keepExistingData?: boolean }) => Promise<{
|
||||
write: (arg: ArrayBuffer | { type: string; position: number; data: ArrayBuffer }) => Promise<void>;
|
||||
close: () => Promise<void>;
|
||||
}>;
|
||||
}>;
|
||||
entries: () => AsyncGenerator<[string]>;
|
||||
removeEntry: (name: string) => Promise<void>;
|
||||
};
|
||||
} {
|
||||
const getFileHandle = async (name: string, opts?: { create?: boolean }) => {
|
||||
if (!files.has(name)) {
|
||||
if (!opts?.create) throw new Error(`NotFoundError: ${name}`);
|
||||
files.set(name, { content: new ArrayBuffer(0) });
|
||||
}
|
||||
const entry = files.get(name)!;
|
||||
return {
|
||||
getFile: async () => ({ size: entry.content.byteLength, arrayBuffer: async () => entry.content }),
|
||||
createWritable: async (wOpts?: { keepExistingData?: boolean }) => {
|
||||
const w: {
|
||||
write: (arg: ArrayBuffer | { type: string; position: number; data: ArrayBuffer }) => Promise<void>;
|
||||
close: () => Promise<void>;
|
||||
} = {
|
||||
write: async (arg) => {
|
||||
const keepExisting = wOpts?.keepExistingData ?? false;
|
||||
const chunk = (arg as { type?: string }).type === 'write'
|
||||
? { data: (arg as { data: ArrayBuffer }).data, position: (arg as { position: number }).position }
|
||||
: { data: arg as ArrayBuffer, position: keepExisting ? entry.content.byteLength : 0 };
|
||||
const merged = new Uint8Array(chunk.position + chunk.data.byteLength);
|
||||
if (keepExisting || chunk.position > 0) {
|
||||
merged.set(new Uint8Array(entry.content), 0);
|
||||
}
|
||||
merged.set(new Uint8Array(chunk.data), chunk.position);
|
||||
entry.content = merged.buffer;
|
||||
},
|
||||
close: async () => { /* no-op */ },
|
||||
};
|
||||
return w;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const dir = {
|
||||
getFileHandle,
|
||||
entries: async function* () {
|
||||
for (const [name] of files) yield [name];
|
||||
},
|
||||
removeEntry: async (name: string) => {
|
||||
files.delete(name);
|
||||
},
|
||||
};
|
||||
|
||||
Object.defineProperty(globalThis, 'navigator', {
|
||||
value: {
|
||||
storage: {
|
||||
getDirectory: async () => ({
|
||||
getDirectoryHandle: async (_name: string, _opts?: unknown) => dir,
|
||||
}),
|
||||
},
|
||||
},
|
||||
configurable: true,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
return { files, dir };
|
||||
}
|
||||
Reference in New Issue
Block a user