fix: bound migration commits and version the OPFS worker protocol
STO-06: the IndexedDB codec migration commit chain runs entirely inside IndexedDB callbacks, so up to maxRows records could keep executing past the caller's cooperative deadline. The monotonic budget is now re-checked before each record's first write; a started record still completes atomically, the checkpoint advances only to the last safe key, and a clock failure aborts the transaction rather than committing an unbounded batch. STO-07: every OPFS worker request and response now carries OPFS_WORKER_PROTOCOL_VERSION = 2, responses echo their request kind, and the client validates the envelope and failure shape strictly while remembering the expected kind per pending request. A page/worker release mismatch or a reply for a different operation closes as UNSUPPORTED instead of being decoded as a value of the wrong shape. UNSUPPORTED is used deliberately: the closed browser-data taxonomy has no INCOMPATIBLE code and none was invented. SW-10 and the OPFS/Web Push V2 wire rollouts remain deferred: they are expand/dual-read/drain/contract deployments across releases rather than a single in-repo change. The ledger records them as DEFERRED_TO_MIGRATION. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
fce8e046ea
commit
f6098242be
@@ -93,8 +93,8 @@ Rollout state starts at `NOT_STARTED`; documented-unimplemented items start at
|
|||||||
| STO-03 | Public cache not composed | `corepack pnpm exec vitest run tests/unit/public-response-cache.test.ts` | `fix: make public cache staging repairable` | `FIXED_NOT_RELEASED` | composition rejection of an existing policy | Red 4 cases across STO-03..05 → green 21/21; `check:types` PASS; `check:browser-file-storage-boundaries` PASS; `lint` PASS |
|
| STO-03 | Public cache not composed | `corepack pnpm exec vitest run tests/unit/public-response-cache.test.ts` | `fix: make public cache staging repairable` | `FIXED_NOT_RELEASED` | composition rejection of an existing policy | Red 4 cases across STO-03..05 → green 21/21; `check:types` PASS; `check:browser-file-storage-boundaries` PASS; `lint` PASS |
|
||||||
| STO-04 | Public cache not composed | `corepack pnpm exec vitest run tests/unit/public-response-cache.test.ts` | `fix: make public cache staging repairable` | `FIXED_NOT_RELEASED` | restage loop or bandwidth spike | — |
|
| STO-04 | Public cache not composed | `corepack pnpm exec vitest run tests/unit/public-response-cache.test.ts` | `fix: make public cache staging repairable` | `FIXED_NOT_RELEASED` | restage loop or bandwidth spike | — |
|
||||||
| STO-05 | Public cache not composed | `corepack pnpm exec vitest run tests/unit/public-response-cache.test.ts` | `fix: make public cache staging repairable` | `FIXED_NOT_RELEASED` | activation permitted without required capability | — |
|
| STO-05 | Public cache not composed | `corepack pnpm exec vitest run tests/unit/public-response-cache.test.ts` | `fix: make public cache staging repairable` | `FIXED_NOT_RELEASED` | activation permitted without required capability | — |
|
||||||
| STO-06 | IndexedDB maintenance | `corepack pnpm exec vitest run tests/unit/indexeddb-maintenance.test.ts` | — | `NOT_STARTED` | migration checkpoint stall | — |
|
| STO-06 | IndexedDB maintenance | `corepack pnpm exec vitest run tests/unit/indexeddb-maintenance.test.ts` | `fix: bound migration commits and version the OPFS worker protocol` | `FIXED_NOT_RELEASED` | migration checkpoint stall | Red commit-phase deadline case → green 13/13; the monotonic budget is re-checked before each record's first write, a started record still finishes atomically, and a clock failure aborts the transaction |
|
||||||
| STO-07 | OPFS worker protocol | `corepack pnpm exec vitest run tests/unit/opfs-worker-runtime.test.ts` | — | `NOT_STARTED` | page/worker `INCOMPATIBLE` spike | — |
|
| STO-07 | OPFS worker protocol | `corepack pnpm exec vitest run tests/unit/opfs-worker-runtime.test.ts` | `fix: bound migration commits and version the OPFS worker protocol` | `FIXED_NOT_RELEASED` | page/worker `INCOMPATIBLE` spike | Green 23/23 across the OPFS suites; every envelope carries `OPFS_WORKER_PROTOCOL_VERSION = 2` and the response echoes its request kind, with a strict failure-shape decoder. A kind or version mismatch closes as `UNSUPPORTED` — the closed taxonomy has no `INCOMPATIBLE` code and none was invented |
|
||||||
| STO-08 | Hypothesis; browser characterization required | `corepack pnpm exec playwright test --config playwright.capabilities.config.ts tests/browser-capabilities/browser-files.spec.ts` | none (source unchanged) | `UNVERIFIED` | n/a until characterized | chromium 2/2 PASS; webkit could not launch (`libevent-2.1-7t64`, `libavif16` missing — environmental). The existing spec does not exercise `Window.showOpenFilePicker`/`showSaveFilePicker`, which need a user gesture and a native dialog, so the receiver-binding hypothesis is **neither reproduced nor refuted**. No `SystemPickerHost` was introduced: the plan forbids implementing an uncharacterized hypothesis as a defect. |
|
| STO-08 | Hypothesis; browser characterization required | `corepack pnpm exec playwright test --config playwright.capabilities.config.ts tests/browser-capabilities/browser-files.spec.ts` | none (source unchanged) | `UNVERIFIED` | n/a until characterized | chromium 2/2 PASS; webkit could not launch (`libevent-2.1-7t64`, `libavif16` missing — environmental). The existing spec does not exercise `Window.showOpenFilePicker`/`showSaveFilePicker`, which need a user gesture and a native dialog, so the receiver-binding hypothesis is **neither reproduced nor refuted**. No `SystemPickerHost` was introduced: the plan forbids implementing an uncharacterized hypothesis as a defect. |
|
||||||
| GAP-01 | Documented unimplemented (VD-15) | promotion evidence, not a red test | — | `PROMOTION_BLOCKED` | n/a | — |
|
| GAP-01 | Documented unimplemented (VD-15) | promotion evidence, not a red test | — | `PROMOTION_BLOCKED` | n/a | — |
|
||||||
| GAP-02 | Documented unimplemented (VD-15) | promotion evidence, not a red test | — | `PROMOTION_BLOCKED` | n/a | — |
|
| GAP-02 | Documented unimplemented (VD-15) | promotion evidence, not a red test | — | `PROMOTION_BLOCKED` | n/a | — |
|
||||||
@@ -148,7 +148,7 @@ Rollout state starts at `NOT_STARTED`; documented-unimplemented items start at
|
|||||||
| SW-07 | Composed when capability selected | `corepack pnpm exec vitest run tests/unit/service-worker-runtime.test.ts` | — | `NOT_STARTED` | activation blocked with zero clients | — |
|
| SW-07 | Composed when capability selected | `corepack pnpm exec vitest run tests/unit/service-worker-runtime.test.ts` | — | `NOT_STARTED` | activation blocked with zero clients | — |
|
||||||
| SW-08 | Composed when capability selected | `corepack pnpm exec vitest run tests/unit/service-worker-runtime.test.ts` | — | `NOT_STARTED` | per-client failure escalation | — |
|
| SW-08 | Composed when capability selected | `corepack pnpm exec vitest run tests/unit/service-worker-runtime.test.ts` | — | `NOT_STARTED` | per-client failure escalation | — |
|
||||||
| SW-09 | Composed when capability selected | `corepack pnpm exec vitest run tests/unit/service-worker-runtime.test.ts` | — | `NOT_STARTED` | late install work observed | — |
|
| SW-09 | Composed when capability selected | `corepack pnpm exec vitest run tests/unit/service-worker-runtime.test.ts` | — | `NOT_STARTED` | late install work observed | — |
|
||||||
| SW-10 | Protocol V2 migration | `corepack pnpm exec vitest run tests/unit/service-worker-runtime.test.ts` | — | `NOT_STARTED` | V1/V2 mismatch fail-close | — |
|
| SW-10 | Protocol V2 migration | `corepack pnpm exec vitest run tests/unit/service-worker-runtime.test.ts` | — | `DEFERRED_TO_MIGRATION` | V1/V2 mismatch fail-close | Not closed here. Full-identity protocol V2 is an expand → dual-read → old-writer drain → contract deployment that spans releases; the prerequisite shared manifest codec and canonical digest landed with SW-05. |
|
||||||
| WP-01 | Web Push `NOT_SELECTED` | `corepack pnpm exec vitest run tests/unit/web-push-fence-store.test.ts` | `fix: bind Web Push mutations to exact authority` | `FIXED_NOT_RELEASED` | CAS receipt rejection | Red stale/skipped/huge revision receipts → green; write and remove share one exact-next-revision validator |
|
| WP-01 | Web Push `NOT_SELECTED` | `corepack pnpm exec vitest run tests/unit/web-push-fence-store.test.ts` | `fix: bind Web Push mutations to exact authority` | `FIXED_NOT_RELEASED` | CAS receipt rejection | Red stale/skipped/huge revision receipts → green; write and remove share one exact-next-revision validator |
|
||||||
| WP-02 | Web Push `NOT_SELECTED` | `corepack pnpm exec vitest run tests/unit/web-push-fence-store.test.ts` | — | `DEFERRED_TO_MIGRATION` | `RECONCILIATION_REQUIRED` backlog | Deferred to the Task 16 versioned-migration PR: `MUTATION_OUTCOME_UNKNOWN` and the `RECONCILIATION_REQUIRED` lifecycle are part of the same wire/data migration as WP-03. |
|
| WP-02 | Web Push `NOT_SELECTED` | `corepack pnpm exec vitest run tests/unit/web-push-fence-store.test.ts` | — | `DEFERRED_TO_MIGRATION` | `RECONCILIATION_REQUIRED` backlog | Deferred to the Task 16 versioned-migration PR: `MUTATION_OUTCOME_UNKNOWN` and the `RECONCILIATION_REQUIRED` lifecycle are part of the same wire/data migration as WP-03. |
|
||||||
| WP-03 | Web Push `NOT_SELECTED` | `corepack pnpm exec vitest run tests/unit/web-push-subscription-adapter.test.ts` | — | `DEFERRED_TO_MIGRATION` | backend receipt mismatch | Deferred to the Task 16 versioned-migration PR: the V2 receipt requires server request-shape negotiation before a client rollout. |
|
| WP-03 | Web Push `NOT_SELECTED` | `corepack pnpm exec vitest run tests/unit/web-push-subscription-adapter.test.ts` | — | `DEFERRED_TO_MIGRATION` | backend receipt mismatch | Deferred to the Task 16 versioned-migration PR: the V2 receipt requires server request-shape negotiation before a client rollout. |
|
||||||
|
|||||||
@@ -972,6 +972,7 @@ export function createIndexedDbMaintenance<WireValue>(
|
|||||||
prepared: readonly PreparedRecord<WireValue>[],
|
prepared: readonly PreparedRecord<WireValue>[],
|
||||||
scan: ScanBatch,
|
scan: ScanBatch,
|
||||||
budgetExhausted: boolean,
|
budgetExhausted: boolean,
|
||||||
|
deadline: number,
|
||||||
signal: AbortSignal | undefined,
|
signal: AbortSignal | undefined,
|
||||||
): Promise<
|
): Promise<
|
||||||
BrowserDataResult<IndexedDbMaintenanceBatchReceipt>
|
BrowserDataResult<IndexedDbMaintenanceBatchReceipt>
|
||||||
@@ -1048,6 +1049,22 @@ export function createIndexedDbMaintenance<WireValue>(
|
|||||||
finishWithCheckpoint();
|
finishWithCheckpoint();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// STO-06. The commit chain runs entirely inside IndexedDB callbacks,
|
||||||
|
// so without this check up to `maxRows` records keep executing past
|
||||||
|
// the caller's invocation deadline. The budget is only ever checked
|
||||||
|
// before a record's first write, so a started record still finishes
|
||||||
|
// atomically and the checkpoint stays exactly at the last safe key.
|
||||||
|
const currentTime = clock();
|
||||||
|
if (!currentTime.ok) {
|
||||||
|
// A broken clock aborts rather than committing an unbounded batch.
|
||||||
|
context.fail(currentTime);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentTime.value >= deadline) {
|
||||||
|
budgetExhausted = true;
|
||||||
|
finishWithCheckpoint();
|
||||||
|
return;
|
||||||
|
}
|
||||||
let request: IDBRequest<unknown>;
|
let request: IDBRequest<unknown>;
|
||||||
try {
|
try {
|
||||||
request = records.get(preparedRecord.source.key);
|
request = records.get(preparedRecord.source.key);
|
||||||
@@ -1337,6 +1354,7 @@ export function createIndexedDbMaintenance<WireValue>(
|
|||||||
prepared.value.records,
|
prepared.value.records,
|
||||||
scan.value,
|
scan.value,
|
||||||
prepared.value.budgetExhausted,
|
prepared.value.budgetExhausted,
|
||||||
|
deadline,
|
||||||
input.signal,
|
input.signal,
|
||||||
);
|
);
|
||||||
return observeResult(
|
return observeResult(
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ import {
|
|||||||
browserDataSuccess,
|
browserDataSuccess,
|
||||||
} from "../../browser-file-storage/result.ts";
|
} from "../../browser-file-storage/result.ts";
|
||||||
import type { OpfsRuntimePolicy } from "./opfs-policy.ts";
|
import type { OpfsRuntimePolicy } from "./opfs-policy.ts";
|
||||||
|
import {
|
||||||
|
OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
|
} from "./opfs-worker-protocol.ts";
|
||||||
import type {
|
import type {
|
||||||
AbortPreparedPutRequest,
|
AbortPreparedPutRequest,
|
||||||
OpfsWorkerGateway,
|
OpfsWorkerGateway,
|
||||||
@@ -51,6 +54,7 @@ export type OwnedOpfsWorkerClient = Readonly<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
type PendingRequest = Readonly<{
|
type PendingRequest = Readonly<{
|
||||||
|
expectedKind: OpfsWorkerRequest["kind"];
|
||||||
resolve: (response: OpfsWorkerResponse) => void;
|
resolve: (response: OpfsWorkerResponse) => void;
|
||||||
reject: (error: OpfsRpcError) => void;
|
reject: (error: OpfsRpcError) => void;
|
||||||
timeout: ReturnType<typeof setTimeout>;
|
timeout: ReturnType<typeof setTimeout>;
|
||||||
@@ -93,6 +97,14 @@ export function createOpfsWorkerGateway(
|
|||||||
pending.delete(event.data.requestId);
|
pending.delete(event.data.requestId);
|
||||||
clearTimeout(request.timeout);
|
clearTimeout(request.timeout);
|
||||||
request.removeAbortListener();
|
request.removeAbortListener();
|
||||||
|
if (event.data.kind !== request.expectedKind) {
|
||||||
|
// STO-07. A reply for a different operation is a protocol breach, not a
|
||||||
|
// value: close it as INCOMPATIBLE instead of decoding it.
|
||||||
|
// UNSUPPORTED is the closed-taxonomy code for "this runtime
|
||||||
|
// cannot serve this"; no new failure code is invented.
|
||||||
|
request.reject(new OpfsRpcError("UNSUPPORTED"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
request.resolve(event.data);
|
request.resolve(event.data);
|
||||||
};
|
};
|
||||||
const onWorkerFailure = (): void => {
|
const onWorkerFailure = (): void => {
|
||||||
@@ -121,7 +133,11 @@ export function createOpfsWorkerGateway(
|
|||||||
) {
|
) {
|
||||||
throw new OpfsRpcError("UNAVAILABLE");
|
throw new OpfsRpcError("UNAVAILABLE");
|
||||||
}
|
}
|
||||||
const message = { ...request, requestId } as OpfsWorkerRequest;
|
const message = {
|
||||||
|
...request,
|
||||||
|
requestId,
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
|
} as OpfsWorkerRequest;
|
||||||
|
|
||||||
return await new Promise<OpfsWorkerResponse>((resolve, reject) => {
|
return await new Promise<OpfsWorkerResponse>((resolve, reject) => {
|
||||||
const abort = (): void => {
|
const abort = (): void => {
|
||||||
@@ -141,6 +157,9 @@ export function createOpfsWorkerGateway(
|
|||||||
reject(new OpfsRpcError("UNAVAILABLE"));
|
reject(new OpfsRpcError("UNAVAILABLE"));
|
||||||
}, dependencies.policy.rpcTimeoutMs);
|
}, dependencies.policy.rpcTimeoutMs);
|
||||||
pending.set(requestId, {
|
pending.set(requestId, {
|
||||||
|
// STO-07. The expected kind is stored so a reply for a different
|
||||||
|
// operation can never satisfy this request.
|
||||||
|
expectedKind: request.kind,
|
||||||
resolve,
|
resolve,
|
||||||
reject,
|
reject,
|
||||||
timeout,
|
timeout,
|
||||||
@@ -677,13 +696,35 @@ function parseOrphanDeleteResult(
|
|||||||
return value as OpfsOrphanDeleteResult;
|
return value as OpfsOrphanDeleteResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* STO-07. A response is only admitted when it carries the negotiated protocol
|
||||||
|
* version, echoes a known request kind and, on failure, a closed failure code.
|
||||||
|
* Accepting `{requestId, ok}` alone let a malformed or cross-release reply be
|
||||||
|
* decoded as a value of the wrong shape.
|
||||||
|
*/
|
||||||
function isWorkerResponse(value: unknown): value is OpfsWorkerResponse {
|
function isWorkerResponse(value: unknown): value is OpfsWorkerResponse {
|
||||||
|
if (
|
||||||
|
!value ||
|
||||||
|
typeof value !== "object" ||
|
||||||
|
!("requestId" in value) ||
|
||||||
|
typeof value.requestId !== "string" ||
|
||||||
|
!("ok" in value) ||
|
||||||
|
typeof value.ok !== "boolean" ||
|
||||||
|
!("protocolVersion" in value) ||
|
||||||
|
value.protocolVersion !== OPFS_WORKER_PROTOCOL_VERSION ||
|
||||||
|
!("kind" in value) ||
|
||||||
|
typeof value.kind !== "string"
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value.ok) return true;
|
||||||
|
const failure = (value as { failure?: unknown }).failure;
|
||||||
return Boolean(
|
return Boolean(
|
||||||
value &&
|
failure &&
|
||||||
typeof value === "object" &&
|
typeof failure === "object" &&
|
||||||
"requestId" in value &&
|
"code" in failure &&
|
||||||
typeof value.requestId === "string" &&
|
typeof (failure as { code?: unknown }).code === "string" &&
|
||||||
"ok" in value &&
|
"retryable" in failure &&
|
||||||
typeof value.ok === "boolean",
|
typeof (failure as { retryable?: unknown }).retryable === "boolean",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,27 @@ import type {
|
|||||||
TransferProgress,
|
TransferProgress,
|
||||||
} from "../../../application/ports/browser-file-storage/shared.ts";
|
} from "../../../application/ports/browser-file-storage/shared.ts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* STO-07. Every request and response carries the protocol version and the
|
||||||
|
* response echoes its request kind, so a page/worker release mismatch or a
|
||||||
|
* malformed reply closes as `INCOMPATIBLE` instead of being decoded as a
|
||||||
|
* successful value of the wrong shape.
|
||||||
|
*/
|
||||||
|
export const OPFS_WORKER_PROTOCOL_VERSION = 2 as const;
|
||||||
|
|
||||||
|
export type OpfsWorkerRequestEnvelope = Readonly<{
|
||||||
|
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
|
||||||
|
}>;
|
||||||
|
|
||||||
export type OpfsWorkerRequest =
|
export type OpfsWorkerRequest =
|
||||||
| Readonly<{
|
| Readonly<{
|
||||||
requestId: string;
|
requestId: string;
|
||||||
|
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
|
||||||
kind: "CAPABILITIES";
|
kind: "CAPABILITIES";
|
||||||
}>
|
}>
|
||||||
| Readonly<{
|
| Readonly<{
|
||||||
requestId: string;
|
requestId: string;
|
||||||
|
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
|
||||||
kind: "BEGIN_PUT";
|
kind: "BEGIN_PUT";
|
||||||
transactionId: string;
|
transactionId: string;
|
||||||
scope: OpfsStorageScope;
|
scope: OpfsStorageScope;
|
||||||
@@ -36,6 +50,7 @@ export type OpfsWorkerRequest =
|
|||||||
}>
|
}>
|
||||||
| Readonly<{
|
| Readonly<{
|
||||||
requestId: string;
|
requestId: string;
|
||||||
|
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
|
||||||
kind: "APPEND_CHUNK";
|
kind: "APPEND_CHUNK";
|
||||||
scope: OpfsStorageScope;
|
scope: OpfsStorageScope;
|
||||||
transactionId: string;
|
transactionId: string;
|
||||||
@@ -44,12 +59,14 @@ export type OpfsWorkerRequest =
|
|||||||
}>
|
}>
|
||||||
| Readonly<{
|
| Readonly<{
|
||||||
requestId: string;
|
requestId: string;
|
||||||
|
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
|
||||||
kind: "FINISH_PUT";
|
kind: "FINISH_PUT";
|
||||||
scope: OpfsStorageScope;
|
scope: OpfsStorageScope;
|
||||||
transactionId: string;
|
transactionId: string;
|
||||||
}>
|
}>
|
||||||
| Readonly<{
|
| Readonly<{
|
||||||
requestId: string;
|
requestId: string;
|
||||||
|
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
|
||||||
kind: "ABORT_PUT";
|
kind: "ABORT_PUT";
|
||||||
scope: OpfsStorageScope;
|
scope: OpfsStorageScope;
|
||||||
transactionId: string;
|
transactionId: string;
|
||||||
@@ -57,17 +74,20 @@ export type OpfsWorkerRequest =
|
|||||||
}>
|
}>
|
||||||
| Readonly<{
|
| Readonly<{
|
||||||
requestId: string;
|
requestId: string;
|
||||||
|
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
|
||||||
kind: "VERIFY_OBJECT";
|
kind: "VERIFY_OBJECT";
|
||||||
preparedObject: OpfsPreparedObject;
|
preparedObject: OpfsPreparedObject;
|
||||||
}>
|
}>
|
||||||
| Readonly<{
|
| Readonly<{
|
||||||
requestId: string;
|
requestId: string;
|
||||||
|
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
|
||||||
kind: "READ_CHUNK";
|
kind: "READ_CHUNK";
|
||||||
preparedObject: OpfsPreparedObject;
|
preparedObject: OpfsPreparedObject;
|
||||||
sequence: number;
|
sequence: number;
|
||||||
}>
|
}>
|
||||||
| Readonly<{
|
| Readonly<{
|
||||||
requestId: string;
|
requestId: string;
|
||||||
|
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
|
||||||
kind: "REMOVE_OBJECT";
|
kind: "REMOVE_OBJECT";
|
||||||
scope: OpfsStorageScope;
|
scope: OpfsStorageScope;
|
||||||
objectId: string;
|
objectId: string;
|
||||||
@@ -75,6 +95,7 @@ export type OpfsWorkerRequest =
|
|||||||
}>
|
}>
|
||||||
| Readonly<{
|
| Readonly<{
|
||||||
requestId: string;
|
requestId: string;
|
||||||
|
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
|
||||||
kind: "CLEANUP_TRANSACTION";
|
kind: "CLEANUP_TRANSACTION";
|
||||||
scope: OpfsStorageScope;
|
scope: OpfsStorageScope;
|
||||||
transactionId: string;
|
transactionId: string;
|
||||||
@@ -87,12 +108,14 @@ export type OpfsWorkerRequest =
|
|||||||
}>
|
}>
|
||||||
| Readonly<{
|
| Readonly<{
|
||||||
requestId: string;
|
requestId: string;
|
||||||
|
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
|
||||||
kind: "FINALIZE_PUT";
|
kind: "FINALIZE_PUT";
|
||||||
transactionId: string;
|
transactionId: string;
|
||||||
preparedObject: OpfsPreparedObject;
|
preparedObject: OpfsPreparedObject;
|
||||||
}>
|
}>
|
||||||
| Readonly<{
|
| Readonly<{
|
||||||
requestId: string;
|
requestId: string;
|
||||||
|
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
|
||||||
kind: "LIST_ORPHAN_CANDIDATES";
|
kind: "LIST_ORPHAN_CANDIDATES";
|
||||||
scope: OpfsStorageScope;
|
scope: OpfsStorageScope;
|
||||||
olderThanEpochMs: number;
|
olderThanEpochMs: number;
|
||||||
@@ -100,6 +123,7 @@ export type OpfsWorkerRequest =
|
|||||||
}>
|
}>
|
||||||
| Readonly<{
|
| Readonly<{
|
||||||
requestId: string;
|
requestId: string;
|
||||||
|
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
|
||||||
kind: "DELETE_ORPHAN_CHUNK";
|
kind: "DELETE_ORPHAN_CHUNK";
|
||||||
scope: OpfsStorageScope;
|
scope: OpfsStorageScope;
|
||||||
digestHex: string;
|
digestHex: string;
|
||||||
@@ -109,7 +133,7 @@ export type OpfsWorkerRequest =
|
|||||||
export type OpfsWorkerRequestBody =
|
export type OpfsWorkerRequestBody =
|
||||||
OpfsWorkerRequest extends infer Request
|
OpfsWorkerRequest extends infer Request
|
||||||
? Request extends OpfsWorkerRequest
|
? Request extends OpfsWorkerRequest
|
||||||
? Omit<Request, "requestId">
|
? Omit<Request, "requestId" | "protocolVersion">
|
||||||
: never
|
: never
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
@@ -132,6 +156,8 @@ export type OpfsOrphanDeleteResult = Readonly<{
|
|||||||
export type OpfsWorkerResponse =
|
export type OpfsWorkerResponse =
|
||||||
| Readonly<{
|
| Readonly<{
|
||||||
requestId: string;
|
requestId: string;
|
||||||
|
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
|
||||||
|
kind: OpfsWorkerRequest["kind"];
|
||||||
ok: true;
|
ok: true;
|
||||||
value?:
|
value?:
|
||||||
| OpfsCapabilities
|
| OpfsCapabilities
|
||||||
@@ -144,6 +170,8 @@ export type OpfsWorkerResponse =
|
|||||||
}>
|
}>
|
||||||
| Readonly<{
|
| Readonly<{
|
||||||
requestId: string;
|
requestId: string;
|
||||||
|
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
|
||||||
|
kind: OpfsWorkerRequest["kind"];
|
||||||
ok: false;
|
ok: false;
|
||||||
failure: OpfsWorkerFailure;
|
failure: OpfsWorkerFailure;
|
||||||
}>;
|
}>;
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ import {
|
|||||||
isValidOpfsStorageScope,
|
isValidOpfsStorageScope,
|
||||||
type OpfsRuntimePolicy,
|
type OpfsRuntimePolicy,
|
||||||
} from "./opfs-policy.ts";
|
} from "./opfs-policy.ts";
|
||||||
|
import {
|
||||||
|
OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
|
} from "./opfs-worker-protocol.ts";
|
||||||
import type {
|
import type {
|
||||||
OpfsOrphanCandidateBatch,
|
OpfsOrphanCandidateBatch,
|
||||||
OpfsOrphanDeleteResult,
|
OpfsOrphanDeleteResult,
|
||||||
@@ -202,18 +205,23 @@ export function createOpfsWorkerRuntime(
|
|||||||
}
|
}
|
||||||
switch (request.kind) {
|
switch (request.kind) {
|
||||||
case "CAPABILITIES":
|
case "CAPABILITIES":
|
||||||
return success(request.requestId, capabilities);
|
return success(request.requestId, request.kind, capabilities);
|
||||||
case "BEGIN_PUT":
|
case "BEGIN_PUT":
|
||||||
await beginPut(request);
|
await beginPut(request);
|
||||||
return success(request.requestId);
|
return success(request.requestId, request.kind);
|
||||||
case "APPEND_CHUNK":
|
case "APPEND_CHUNK":
|
||||||
await appendChunk(request);
|
await appendChunk(request);
|
||||||
return success(request.requestId);
|
return success(request.requestId, request.kind);
|
||||||
case "FINISH_PUT":
|
case "FINISH_PUT":
|
||||||
return success(request.requestId, await finishPut(request));
|
return success(
|
||||||
|
request.requestId,
|
||||||
|
request.kind,
|
||||||
|
await finishPut(request),
|
||||||
|
);
|
||||||
case "ABORT_PUT":
|
case "ABORT_PUT":
|
||||||
return success(
|
return success(
|
||||||
request.requestId,
|
request.requestId,
|
||||||
|
request.kind,
|
||||||
await abortPut(
|
await abortPut(
|
||||||
request.scope,
|
request.scope,
|
||||||
request.transactionId,
|
request.transactionId,
|
||||||
@@ -223,11 +231,13 @@ export function createOpfsWorkerRuntime(
|
|||||||
case "VERIFY_OBJECT":
|
case "VERIFY_OBJECT":
|
||||||
return success(
|
return success(
|
||||||
request.requestId,
|
request.requestId,
|
||||||
|
request.kind,
|
||||||
await verifyObject(request.preparedObject),
|
await verifyObject(request.preparedObject),
|
||||||
);
|
);
|
||||||
case "READ_CHUNK":
|
case "READ_CHUNK":
|
||||||
return success(
|
return success(
|
||||||
request.requestId,
|
request.requestId,
|
||||||
|
request.kind,
|
||||||
await readVerifiedChunk(
|
await readVerifiedChunk(
|
||||||
request.preparedObject,
|
request.preparedObject,
|
||||||
request.sequence,
|
request.sequence,
|
||||||
@@ -239,10 +249,11 @@ export function createOpfsWorkerRuntime(
|
|||||||
request.objectId,
|
request.objectId,
|
||||||
request.generation,
|
request.generation,
|
||||||
);
|
);
|
||||||
return success(request.requestId);
|
return success(request.requestId, request.kind);
|
||||||
case "CLEANUP_TRANSACTION":
|
case "CLEANUP_TRANSACTION":
|
||||||
return success(
|
return success(
|
||||||
request.requestId,
|
request.requestId,
|
||||||
|
request.kind,
|
||||||
await cleanupTransaction(
|
await cleanupTransaction(
|
||||||
request.scope,
|
request.scope,
|
||||||
request.transactionId,
|
request.transactionId,
|
||||||
@@ -255,10 +266,11 @@ export function createOpfsWorkerRuntime(
|
|||||||
request.transactionId,
|
request.transactionId,
|
||||||
request.preparedObject,
|
request.preparedObject,
|
||||||
);
|
);
|
||||||
return success(request.requestId);
|
return success(request.requestId, request.kind);
|
||||||
case "LIST_ORPHAN_CANDIDATES":
|
case "LIST_ORPHAN_CANDIDATES":
|
||||||
return success(
|
return success(
|
||||||
request.requestId,
|
request.requestId,
|
||||||
|
request.kind,
|
||||||
await listOrphanCandidates(
|
await listOrphanCandidates(
|
||||||
request.scope,
|
request.scope,
|
||||||
request.olderThanEpochMs,
|
request.olderThanEpochMs,
|
||||||
@@ -268,6 +280,7 @@ export function createOpfsWorkerRuntime(
|
|||||||
case "DELETE_ORPHAN_CHUNK":
|
case "DELETE_ORPHAN_CHUNK":
|
||||||
return success(
|
return success(
|
||||||
request.requestId,
|
request.requestId,
|
||||||
|
request.kind,
|
||||||
await deleteOrphanChunk(
|
await deleteOrphanChunk(
|
||||||
request.scope,
|
request.scope,
|
||||||
request.digestHex,
|
request.digestHex,
|
||||||
@@ -1747,6 +1760,7 @@ function isPreparedObjectSafe(
|
|||||||
|
|
||||||
function success(
|
function success(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
|
kind: OpfsWorkerRequest["kind"],
|
||||||
value?: OpfsWorkerResponse extends infer _Response
|
value?: OpfsWorkerResponse extends infer _Response
|
||||||
?
|
?
|
||||||
| OpfsCapabilities
|
| OpfsCapabilities
|
||||||
@@ -1759,15 +1773,33 @@ function success(
|
|||||||
: never,
|
: never,
|
||||||
): OpfsWorkerResponse {
|
): OpfsWorkerResponse {
|
||||||
return value === undefined
|
return value === undefined
|
||||||
? { requestId, ok: true }
|
? {
|
||||||
: { requestId, ok: true, value };
|
requestId,
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
|
kind,
|
||||||
|
ok: true,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
requestId,
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
|
kind,
|
||||||
|
ok: true,
|
||||||
|
value,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function failure(
|
function failure(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workerFailure: OpfsWorkerFailure,
|
workerFailure: OpfsWorkerFailure,
|
||||||
|
kind: OpfsWorkerRequest["kind"] = "CAPABILITIES",
|
||||||
): OpfsWorkerResponse {
|
): OpfsWorkerResponse {
|
||||||
return { requestId, ok: false, failure: workerFailure };
|
return {
|
||||||
|
requestId,
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
|
kind,
|
||||||
|
ok: false,
|
||||||
|
failure: workerFailure,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapRuntimeFailure(error: unknown): OpfsWorkerFailure {
|
function mapRuntimeFailure(error: unknown): OpfsWorkerFailure {
|
||||||
@@ -1829,7 +1861,10 @@ function isWorkerRequest(value: unknown): value is OpfsWorkerRequest {
|
|||||||
hasRequestId(value) &&
|
hasRequestId(value) &&
|
||||||
"kind" in value &&
|
"kind" in value &&
|
||||||
typeof value.kind === "string" &&
|
typeof value.kind === "string" &&
|
||||||
WORKER_REQUEST_KINDS.has(value.kind),
|
WORKER_REQUEST_KINDS.has(value.kind) &&
|
||||||
|
// STO-07. A page from another release must not be served.
|
||||||
|
"protocolVersion" in value &&
|
||||||
|
value.protocolVersion === OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -596,6 +596,37 @@ describe("IndexedDB bounded codec maintenance", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("stops codec migration commit at the cooperative deadline", async () => {
|
||||||
|
const memory = new MemoryIndexedDbFactory();
|
||||||
|
await prepareSchema(memory);
|
||||||
|
seedLegacy(memory, "row-a", "first");
|
||||||
|
seedLegacy(memory, "row-b", "second");
|
||||||
|
const policy = defaultPolicy();
|
||||||
|
// STO-06. The clock only advances past the deadline once the commit
|
||||||
|
// transaction is already open, so the stop must happen inside the commit
|
||||||
|
// chain rather than before transform.
|
||||||
|
let calls = 0;
|
||||||
|
const maintenance = createMaintenance(memory, policy, {
|
||||||
|
now: () => {
|
||||||
|
calls += 1;
|
||||||
|
// Scan, prepare and the first commit record stay inside the budget.
|
||||||
|
return calls <= 6 ? 0 : 5_000;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await maintenance.migrateCodecBatch({
|
||||||
|
maxRows: 10,
|
||||||
|
maxDurationMs: 1_000,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.ok).toBe(true);
|
||||||
|
if (!result.ok) return;
|
||||||
|
// The batch is incomplete and says so; it never claims a full pass.
|
||||||
|
expect(result.value.state).toBe("MORE");
|
||||||
|
expect(result.value.budgetExhausted).toBe(true);
|
||||||
|
expect(result.value.checkpointedRows).toBeLessThan(2);
|
||||||
|
});
|
||||||
|
|
||||||
it("fails closed when a historical payload cannot be transformed", async () => {
|
it("fails closed when a historical payload cannot be transformed", async () => {
|
||||||
const memory = new MemoryIndexedDbFactory();
|
const memory = new MemoryIndexedDbFactory();
|
||||||
await prepareSchema(memory);
|
await prepareSchema(memory);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
createOpfsWorkerGateway,
|
createOpfsWorkerGateway,
|
||||||
type OpfsWorkerLike,
|
type OpfsWorkerLike,
|
||||||
} from "../../src/adapters/storage/opfs/opfs-worker-client.ts";
|
} from "../../src/adapters/storage/opfs/opfs-worker-client.ts";
|
||||||
|
import { OPFS_WORKER_PROTOCOL_VERSION } from "../../src/adapters/storage/opfs/opfs-worker-protocol.ts";
|
||||||
import type {
|
import type {
|
||||||
OpfsWorkerRequest,
|
OpfsWorkerRequest,
|
||||||
OpfsWorkerResponse,
|
OpfsWorkerResponse,
|
||||||
@@ -181,6 +182,7 @@ function beginRequest(
|
|||||||
): OpfsWorkerRequest {
|
): OpfsWorkerRequest {
|
||||||
return {
|
return {
|
||||||
requestId,
|
requestId,
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
kind: "BEGIN_PUT",
|
kind: "BEGIN_PUT",
|
||||||
transactionId,
|
transactionId,
|
||||||
scope,
|
scope,
|
||||||
@@ -268,6 +270,7 @@ describe("OPFS dedicated worker runtime", () => {
|
|||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
const aborted = await runtime.handleRequest({
|
const aborted = await runtime.handleRequest({
|
||||||
requestId: "request_abort_1234",
|
requestId: "request_abort_1234",
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
kind: "ABORT_PUT",
|
kind: "ABORT_PUT",
|
||||||
scope: scopeA,
|
scope: scopeA,
|
||||||
transactionId: "transaction_12345678",
|
transactionId: "transaction_12345678",
|
||||||
@@ -319,6 +322,7 @@ describe("OPFS dedicated worker runtime", () => {
|
|||||||
|
|
||||||
const appending = runtime.handleRequest({
|
const appending = runtime.handleRequest({
|
||||||
requestId: "request_append_5678",
|
requestId: "request_append_5678",
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
kind: "APPEND_CHUNK",
|
kind: "APPEND_CHUNK",
|
||||||
scope: scopeA,
|
scope: scopeA,
|
||||||
transactionId: "transaction_56785678",
|
transactionId: "transaction_56785678",
|
||||||
@@ -328,6 +332,7 @@ describe("OPFS dedicated worker runtime", () => {
|
|||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
const aborting = runtime.handleRequest({
|
const aborting = runtime.handleRequest({
|
||||||
requestId: "request_abort_5678",
|
requestId: "request_abort_5678",
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
kind: "ABORT_PUT",
|
kind: "ABORT_PUT",
|
||||||
scope: scopeA,
|
scope: scopeA,
|
||||||
transactionId: "transaction_56785678",
|
transactionId: "transaction_56785678",
|
||||||
@@ -374,6 +379,7 @@ describe("OPFS dedicated worker runtime", () => {
|
|||||||
expect(
|
expect(
|
||||||
await runtime.handleRequest({
|
await runtime.handleRequest({
|
||||||
requestId: `request_append_iso_${index}`,
|
requestId: `request_append_iso_${index}`,
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
kind: "APPEND_CHUNK",
|
kind: "APPEND_CHUNK",
|
||||||
scope: targetScope,
|
scope: targetScope,
|
||||||
transactionId,
|
transactionId,
|
||||||
@@ -386,6 +392,7 @@ describe("OPFS dedicated worker runtime", () => {
|
|||||||
expect(
|
expect(
|
||||||
await runtime.handleRequest({
|
await runtime.handleRequest({
|
||||||
requestId: "request_abort_iso_a",
|
requestId: "request_abort_iso_a",
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
kind: "ABORT_PUT",
|
kind: "ABORT_PUT",
|
||||||
scope: scopeA,
|
scope: scopeA,
|
||||||
transactionId,
|
transactionId,
|
||||||
@@ -393,6 +400,7 @@ describe("OPFS dedicated worker runtime", () => {
|
|||||||
).toMatchObject({ ok: true });
|
).toMatchObject({ ok: true });
|
||||||
const finished = await runtime.handleRequest({
|
const finished = await runtime.handleRequest({
|
||||||
requestId: "request_finish_iso_b",
|
requestId: "request_finish_iso_b",
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
kind: "FINISH_PUT",
|
kind: "FINISH_PUT",
|
||||||
scope: scopeB,
|
scope: scopeB,
|
||||||
transactionId,
|
transactionId,
|
||||||
@@ -402,11 +410,14 @@ describe("OPFS dedicated worker runtime", () => {
|
|||||||
expect(
|
expect(
|
||||||
await runtime.handleRequest({
|
await runtime.handleRequest({
|
||||||
requestId: "request_verify_iso_b",
|
requestId: "request_verify_iso_b",
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
kind: "VERIFY_OBJECT",
|
kind: "VERIFY_OBJECT",
|
||||||
preparedObject,
|
preparedObject,
|
||||||
}),
|
}),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
requestId: "request_verify_iso_b",
|
requestId: "request_verify_iso_b",
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
|
kind: expect.any(String),
|
||||||
ok: true,
|
ok: true,
|
||||||
value: true,
|
value: true,
|
||||||
});
|
});
|
||||||
@@ -435,6 +446,7 @@ describe("OPFS dedicated worker runtime", () => {
|
|||||||
);
|
);
|
||||||
await runtime.handleRequest({
|
await runtime.handleRequest({
|
||||||
requestId: `request_gc_append_${index}`,
|
requestId: `request_gc_append_${index}`,
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
kind: "APPEND_CHUNK",
|
kind: "APPEND_CHUNK",
|
||||||
scope: targetScope,
|
scope: targetScope,
|
||||||
transactionId,
|
transactionId,
|
||||||
@@ -444,6 +456,7 @@ describe("OPFS dedicated worker runtime", () => {
|
|||||||
const object = preparedValue(
|
const object = preparedValue(
|
||||||
await runtime.handleRequest({
|
await runtime.handleRequest({
|
||||||
requestId: `request_gc_finish_${index}`,
|
requestId: `request_gc_finish_${index}`,
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
kind: "FINISH_PUT",
|
kind: "FINISH_PUT",
|
||||||
scope: targetScope,
|
scope: targetScope,
|
||||||
transactionId,
|
transactionId,
|
||||||
@@ -452,6 +465,7 @@ describe("OPFS dedicated worker runtime", () => {
|
|||||||
preparedByScope.set(targetScope.authorityToken, object);
|
preparedByScope.set(targetScope.authorityToken, object);
|
||||||
await runtime.handleRequest({
|
await runtime.handleRequest({
|
||||||
requestId: `request_gc_finalize_${index}`,
|
requestId: `request_gc_finalize_${index}`,
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
kind: "FINALIZE_PUT",
|
kind: "FINALIZE_PUT",
|
||||||
transactionId,
|
transactionId,
|
||||||
preparedObject: object,
|
preparedObject: object,
|
||||||
@@ -464,6 +478,7 @@ describe("OPFS dedicated worker runtime", () => {
|
|||||||
expect(
|
expect(
|
||||||
await runtime.handleRequest({
|
await runtime.handleRequest({
|
||||||
requestId: "request_gc_list_a",
|
requestId: "request_gc_list_a",
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
kind: "LIST_ORPHAN_CANDIDATES",
|
kind: "LIST_ORPHAN_CANDIDATES",
|
||||||
scope: scopeA,
|
scope: scopeA,
|
||||||
olderThanEpochMs: cutoff,
|
olderThanEpochMs: cutoff,
|
||||||
@@ -473,6 +488,7 @@ describe("OPFS dedicated worker runtime", () => {
|
|||||||
expect(
|
expect(
|
||||||
await runtime.handleRequest({
|
await runtime.handleRequest({
|
||||||
requestId: "request_gc_delete_a",
|
requestId: "request_gc_delete_a",
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
kind: "DELETE_ORPHAN_CHUNK",
|
kind: "DELETE_ORPHAN_CHUNK",
|
||||||
scope: scopeA,
|
scope: scopeA,
|
||||||
digestHex,
|
digestHex,
|
||||||
@@ -483,22 +499,28 @@ describe("OPFS dedicated worker runtime", () => {
|
|||||||
expect(
|
expect(
|
||||||
await runtime.handleRequest({
|
await runtime.handleRequest({
|
||||||
requestId: "request_gc_verify_a",
|
requestId: "request_gc_verify_a",
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
kind: "VERIFY_OBJECT",
|
kind: "VERIFY_OBJECT",
|
||||||
preparedObject: preparedByScope.get(scopeA.authorityToken)!,
|
preparedObject: preparedByScope.get(scopeA.authorityToken)!,
|
||||||
}),
|
}),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
requestId: "request_gc_verify_a",
|
requestId: "request_gc_verify_a",
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
|
kind: expect.any(String),
|
||||||
ok: true,
|
ok: true,
|
||||||
value: false,
|
value: false,
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
await runtime.handleRequest({
|
await runtime.handleRequest({
|
||||||
requestId: "request_gc_verify_b",
|
requestId: "request_gc_verify_b",
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
kind: "VERIFY_OBJECT",
|
kind: "VERIFY_OBJECT",
|
||||||
preparedObject: preparedByScope.get(scopeB.authorityToken)!,
|
preparedObject: preparedByScope.get(scopeB.authorityToken)!,
|
||||||
}),
|
}),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
requestId: "request_gc_verify_b",
|
requestId: "request_gc_verify_b",
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
|
kind: expect.any(String),
|
||||||
ok: true,
|
ok: true,
|
||||||
value: true,
|
value: true,
|
||||||
});
|
});
|
||||||
@@ -516,10 +538,13 @@ describe("OPFS dedicated worker runtime", () => {
|
|||||||
expect(
|
expect(
|
||||||
await runtime.handleRequest({
|
await runtime.handleRequest({
|
||||||
requestId: "request_caps_1234",
|
requestId: "request_caps_1234",
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
kind: "CAPABILITIES",
|
kind: "CAPABILITIES",
|
||||||
}),
|
}),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
requestId: "request_caps_1234",
|
requestId: "request_caps_1234",
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
|
kind: expect.any(String),
|
||||||
ok: true,
|
ok: true,
|
||||||
value: {
|
value: {
|
||||||
available: false,
|
available: false,
|
||||||
@@ -569,6 +594,8 @@ describe("OPFS worker client lifecycle", () => {
|
|||||||
listener?.({
|
listener?.({
|
||||||
data: {
|
data: {
|
||||||
requestId: "request_collision_1234",
|
requestId: "request_collision_1234",
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
|
kind: "CAPABILITIES",
|
||||||
ok: true,
|
ok: true,
|
||||||
value: {
|
value: {
|
||||||
available: true,
|
available: true,
|
||||||
@@ -633,11 +660,15 @@ describe("OPFS worker client lifecycle", () => {
|
|||||||
message.kind === "VERIFY_OBJECT"
|
message.kind === "VERIFY_OBJECT"
|
||||||
? {
|
? {
|
||||||
requestId: message.requestId,
|
requestId: message.requestId,
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
|
kind: message.kind,
|
||||||
ok: true,
|
ok: true,
|
||||||
value: true,
|
value: true,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
requestId: message.requestId,
|
requestId: message.requestId,
|
||||||
|
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
|
||||||
|
kind: message.kind,
|
||||||
ok: true,
|
ok: true,
|
||||||
value: new Uint8Array([4, 2]).buffer,
|
value: new Uint8Array([4, 2]).buffer,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user