feat: 기능 추가 과정중
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
import { expect, test } from "../support/browser/strict-browser-test.ts";
|
||||
|
||||
test("runs put, open, remove and reconcile through a native DedicatedWorker, OPFS and IndexedDB", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/");
|
||||
const result = await page.evaluate(async () => {
|
||||
const runtimeModulePath =
|
||||
"/src/adapters/storage/opfs/browser-opfs-runtime.ts";
|
||||
const policyModulePath =
|
||||
"/src/adapters/storage/opfs/opfs-policy.ts";
|
||||
const journalModulePath =
|
||||
"/src/adapters/storage/opfs/indexeddb-opfs-journal.ts";
|
||||
const {
|
||||
createBrowserOpfsRuntime,
|
||||
inspectBrowserOpfsSupport,
|
||||
} = (await import(
|
||||
/* @vite-ignore */ runtimeModulePath
|
||||
)) as typeof import("../../src/adapters/storage/opfs/browser-opfs-runtime.ts");
|
||||
const {
|
||||
DEFAULT_OPFS_RUNTIME_POLICY,
|
||||
resolveOpfsRuntimePolicy,
|
||||
} = (await import(
|
||||
/* @vite-ignore */ policyModulePath
|
||||
)) as typeof import("../../src/adapters/storage/opfs/opfs-policy.ts");
|
||||
const { opfsJournalDatabaseName } = (await import(
|
||||
/* @vite-ignore */ journalModulePath
|
||||
)) as typeof import("../../src/adapters/storage/opfs/indexeddb-opfs-journal.ts");
|
||||
const policy = resolveOpfsRuntimePolicy();
|
||||
const support = inspectBrowserOpfsSupport(policy);
|
||||
if (!support.ok) {
|
||||
return {
|
||||
nativeSupport: false as const,
|
||||
capabilities: support,
|
||||
};
|
||||
}
|
||||
|
||||
const suffix = crypto.randomUUID().replaceAll("-", "");
|
||||
const scope = {
|
||||
namespace: "durable-objects",
|
||||
authorityToken: `authority_${suffix}`,
|
||||
namespaceToken: `namespace_${suffix}`,
|
||||
partitionToken: `partition_${suffix}`,
|
||||
} as const;
|
||||
const storagePolicy = {
|
||||
owner: "browser-capability",
|
||||
namespace: scope.namespace,
|
||||
classification: "PERSONAL",
|
||||
authority: "LOCAL_FIRST",
|
||||
accountScope: "OPAQUE_PARTITION",
|
||||
retention: { kind: "EXPLICIT_DELETE" },
|
||||
softBudgetBytes: 1024 * 1024,
|
||||
hardBudgetBytes: 2 * 1024 * 1024,
|
||||
evictionPriority: "USER_AUTHORED",
|
||||
logoutAction: "EXPORT_THEN_PURGE",
|
||||
accountDeletionAction: "PURGE_PARTITION",
|
||||
pressureAction: "RETAIN",
|
||||
unavailableFallback: "EXPORT_REQUIRED",
|
||||
} as const;
|
||||
const runtime = createBrowserOpfsRuntime({
|
||||
workerUrl: new URL(
|
||||
"/tests/browser-capabilities/opfs-test.worker.ts",
|
||||
location.href,
|
||||
),
|
||||
workerName: `opfs-capability-${suffix}`,
|
||||
scope,
|
||||
storagePolicy,
|
||||
policy,
|
||||
});
|
||||
const bytes = new Uint8Array([3, 1, 4, 1, 5, 9]);
|
||||
let capabilities: unknown;
|
||||
let put: unknown;
|
||||
let opened:
|
||||
| Awaited<ReturnType<typeof runtime.objects.open>>
|
||||
| undefined;
|
||||
let readBytes: number[] = [];
|
||||
let removed: unknown;
|
||||
let afterRemove: unknown;
|
||||
let reconciled: unknown;
|
||||
try {
|
||||
capabilities = await runtime.objects.capabilities();
|
||||
put = await runtime.objects.put({
|
||||
objectId: "object_browser_12345678",
|
||||
expectedGeneration: null,
|
||||
mediaType: "application/octet-stream",
|
||||
source: {
|
||||
byteLength: bytes.byteLength,
|
||||
async *stream(signal: AbortSignal) {
|
||||
if (signal.aborted) {
|
||||
yield {
|
||||
ok: false as const,
|
||||
error: {
|
||||
code: "ABORTED" as const,
|
||||
operation: "OBJECT_WRITE" as const,
|
||||
retryable: false,
|
||||
recovery: "NONE" as const,
|
||||
},
|
||||
};
|
||||
return;
|
||||
}
|
||||
yield { ok: true as const, value: Uint8Array.from(bytes) };
|
||||
},
|
||||
},
|
||||
});
|
||||
opened = await runtime.objects.open({
|
||||
objectId: "object_browser_12345678",
|
||||
});
|
||||
if (
|
||||
opened &&
|
||||
typeof opened === "object" &&
|
||||
"ok" in opened &&
|
||||
opened.ok
|
||||
) {
|
||||
for await (const chunk of opened.value.source.stream(
|
||||
new AbortController().signal,
|
||||
)) {
|
||||
if (!chunk.ok) throw new Error(chunk.error.code);
|
||||
readBytes.push(...chunk.value);
|
||||
}
|
||||
}
|
||||
removed = await runtime.objects.remove({
|
||||
objectId: "object_browser_12345678",
|
||||
expectedGeneration: 1,
|
||||
});
|
||||
afterRemove = await runtime.objects.open({
|
||||
objectId: "object_browser_12345678",
|
||||
});
|
||||
reconciled = await runtime.maintenance.reconcile({
|
||||
budgetMs: 5_000,
|
||||
maxTransactions: 100,
|
||||
});
|
||||
} finally {
|
||||
runtime.close();
|
||||
}
|
||||
|
||||
const deletion = await new Promise<string>((resolve) => {
|
||||
const request = indexedDB.deleteDatabase(
|
||||
opfsJournalDatabaseName(scope.authorityToken),
|
||||
);
|
||||
request.onsuccess = () => resolve("DELETED");
|
||||
request.onerror = () =>
|
||||
resolve(request.error?.name ?? "UNKNOWN_ERROR");
|
||||
request.onblocked = () => resolve("BLOCKED");
|
||||
});
|
||||
let opfsCleanup = "ABSENT";
|
||||
try {
|
||||
const originRoot = await navigator.storage.getDirectory();
|
||||
const runtimeRoot = await originRoot.getDirectoryHandle(
|
||||
DEFAULT_OPFS_RUNTIME_POLICY.rootDirectoryName,
|
||||
);
|
||||
const authorities = await runtimeRoot.getDirectoryHandle(
|
||||
"authorities",
|
||||
);
|
||||
await authorities.removeEntry(scope.authorityToken, {
|
||||
recursive: true,
|
||||
});
|
||||
opfsCleanup = "DELETED";
|
||||
} catch (error) {
|
||||
if (!(error instanceof DOMException) || error.name !== "NotFoundError") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return {
|
||||
nativeSupport: true as const,
|
||||
capabilities,
|
||||
put,
|
||||
opened:
|
||||
opened &&
|
||||
typeof opened === "object" &&
|
||||
"ok" in opened &&
|
||||
opened.ok
|
||||
? {
|
||||
ok: true,
|
||||
generation: opened.value.descriptor.generation,
|
||||
}
|
||||
: opened,
|
||||
readBytes,
|
||||
removed,
|
||||
afterRemove,
|
||||
reconciled,
|
||||
deletion,
|
||||
opfsCleanup,
|
||||
};
|
||||
});
|
||||
|
||||
if (!result.nativeSupport) {
|
||||
expect(result.capabilities).toMatchObject({
|
||||
ok: false,
|
||||
error: { code: "UNSUPPORTED" },
|
||||
});
|
||||
return;
|
||||
}
|
||||
expect(result.capabilities).toMatchObject({
|
||||
ok: true,
|
||||
value: {
|
||||
available: true,
|
||||
dedicatedWorkerRequired: true,
|
||||
crossContextMutationLockAvailable: true,
|
||||
},
|
||||
});
|
||||
expect(result.put).toMatchObject({
|
||||
ok: true,
|
||||
value: { generation: 1, byteLength: 6 },
|
||||
});
|
||||
expect(result.opened).toEqual({ ok: true, generation: 1 });
|
||||
expect(result.readBytes).toEqual([3, 1, 4, 1, 5, 9]);
|
||||
expect(result.removed).toEqual({ ok: true });
|
||||
expect(result.afterRemove).toMatchObject({
|
||||
ok: false,
|
||||
error: { code: "NOT_FOUND" },
|
||||
});
|
||||
expect(result.reconciled).toMatchObject({
|
||||
ok: true,
|
||||
value: {
|
||||
inspectedTransactions: 0,
|
||||
orphanGcStatus: "COMPLETED",
|
||||
},
|
||||
});
|
||||
expect(result.deletion).toBe("DELETED");
|
||||
expect(["DELETED", "ABSENT"]).toContain(result.opfsCleanup);
|
||||
});
|
||||
Reference in New Issue
Block a user