{"version":3,"file":"gating-storage.d.ts","sourceRoot":"","sources":["../../../../src/harness/session/testing/gating-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,qEAAqE;AACrE,qBAAa,eAAgB,SAAQ,KAAK;IACzC,YAAY,OAAO,EAAE,MAAM,EAG1B;CACD;AAcD,iFAAiF;AACjF,qBAAa,aAAc,SAAQ,gBAAgB;IAClD,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAE/C,4DAA4D;IAC5D,GAAG,IAAI,IAAI,CAEV;IAED,OAAO,IAAI,MAAM,CAEhB;IAED,sDAAsD;IACtD,WAAW,CAAC,KAAK,SAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CASpC;IAEc,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CA4B9E;IAED,6EAA6E;IACvE,IAAI,CAAC,KAAK,SAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAWnC;IAED,qEAAqE;IACrE,OAAO,IAAI,IAAI,CAMd;IAED,OAAO,CAAC,aAAa;CAQrB","sourcesContent":["import type { Context } from \"../../context.ts\";\nimport type { CommitResult, Write } from \"../types.ts\";\nimport { StorageDecorator } from \"./storage-decorator.ts\";\n\n/** Thrown for every commit rejected after simulated storage loss. */\nexport class CommitDiscarded extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"CommitDiscarded\";\n\t}\n}\n\ninterface ParkedCommit {\n\trelease(): void;\n\tdrop(error: Error): void;\n\treadonly landing: Promise<void>;\n}\n\ninterface PendingWaiter {\n\tcount: number;\n\tresolve(): void;\n\treject(error: Error): void;\n}\n\n/** Test-only storage decorator that deterministically parks admitted commits. */\nexport class GatingStorage extends StorageDecorator {\n\tprivate armed = false;\n\tprivate discarded = false;\n\tprivate readonly queue: ParkedCommit[] = [];\n\tprivate readonly waiters: PendingWaiter[] = [];\n\n\t/** Fixture setup bypasses gating until explicitly armed. */\n\tarm(): void {\n\t\tthis.armed = true;\n\t}\n\n\tpending(): number {\n\t\treturn this.queue.length;\n\t}\n\n\t/** Wait until at least `count` commits are parked. */\n\twaitPending(count = 1): Promise<void> {\n\t\tif (!Number.isSafeInteger(count) || count < 1) {\n\t\t\treturn Promise.reject(new RangeError(\"Pending commit count must be a positive safe integer\"));\n\t\t}\n\t\tif (this.discarded) return Promise.reject(new CommitDiscarded(\"storage discarded\"));\n\t\tif (this.queue.length >= count) return Promise.resolve();\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tthis.waiters.push({ count, resolve, reject });\n\t\t});\n\t}\n\n\toverride async commit(writes: Write[], context: Context): Promise<CommitResult> {\n\t\tif (this.discarded) throw new CommitDiscarded(\"commit rejected: storage discarded\");\n\t\tif (!this.armed) return super.commit(writes, context);\n\n\t\tlet landed!: () => void;\n\t\tlet lost!: (error: Error) => void;\n\t\tconst landing = new Promise<void>((resolve, reject) => {\n\t\t\tlanded = resolve;\n\t\t\tlost = reject;\n\t\t});\n\t\tvoid landing.catch(() => {});\n\n\t\tconst released = new Promise<void>((resolve, reject) => {\n\t\t\tthis.queue.push({ release: resolve, drop: reject, landing });\n\t\t});\n\t\tthis.notifyWaiters();\n\n\t\ttry {\n\t\t\tawait released;\n\t\t\tif (this.discarded) throw new CommitDiscarded(\"commit rejected: storage discarded\");\n\t\t\tconst result = await super.commit(writes, context);\n\t\t\tlanded();\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\tconst normalized = error instanceof Error ? error : new Error(String(error));\n\t\t\tlost(normalized);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/** Release `count` commits in FIFO order and wait until each write lands. */\n\tasync next(count = 1): Promise<void> {\n\t\tif (!Number.isSafeInteger(count) || count < 1) {\n\t\t\tthrow new RangeError(\"Released commit count must be a positive safe integer\");\n\t\t}\n\t\tfor (let index = 0; index < count; index++) {\n\t\t\tawait this.waitPending();\n\t\t\tconst parked = this.queue.shift();\n\t\t\tif (parked === undefined) throw new Error(\"No parked commit\");\n\t\t\tparked.release();\n\t\t\tawait parked.landing;\n\t\t}\n\t}\n\n\t/** Drop parked commits and permanently reject every later commit. */\n\tdiscard(): void {\n\t\tif (this.discarded) return;\n\t\tthis.discarded = true;\n\t\tconst error = new CommitDiscarded(\"commit discarded\");\n\t\tfor (const parked of this.queue.splice(0)) parked.drop(error);\n\t\tfor (const waiter of this.waiters.splice(0)) waiter.reject(error);\n\t}\n\n\tprivate notifyWaiters(): void {\n\t\tfor (let index = this.waiters.length - 1; index >= 0; index--) {\n\t\t\tconst waiter = this.waiters[index]!;\n\t\t\tif (this.queue.length < waiter.count) continue;\n\t\t\tthis.waiters.splice(index, 1);\n\t\t\twaiter.resolve();\n\t\t}\n\t}\n}\n"]}