@noego/testing 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +721 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +262 -0
- package/dist/index.d.ts +262 -0
- package/dist/index.js +676 -0
- package/dist/index.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/time/manual_clock.ts","../src/time/manual_scheduler.ts","../src/identity/sequence_id_generator.ts","../src/identity/seeded_random_source.ts","../src/network/scripted_fetch_client.ts","../src/process/scripted_process_runner.ts","../src/storage/memory_key_value_store.ts","../src/storage/memory_object_store.ts","../src/events/recording_event_bus.ts","../src/observability/recording_sinks.ts","../src/contracts/clock.contract.ts","../src/contracts/key_value_store.contract.ts","../src/contracts/object_store.contract.ts","../src/contracts/event_bus.contract.ts","../src/leak/leak_detector.ts"],"sourcesContent":["export { ManualClock } from \"./time/manual_clock.js\";\nexport { ManualScheduler } from \"./time/manual_scheduler.js\";\nexport type { PendingTask } from \"./time/manual_scheduler.js\";\n\nexport { SequenceIdGenerator } from \"./identity/sequence_id_generator.js\";\nexport { SeededRandomSource } from \"./identity/seeded_random_source.js\";\n\nexport { ScriptedFetchClient } from \"./network/scripted_fetch_client.js\";\nexport type { FetchScriptEntry } from \"./network/scripted_fetch_client.js\";\n\nexport { ScriptedProcessRunner } from \"./process/scripted_process_runner.js\";\nexport type { ProcessScriptEntry, ScriptedSpawnHandle } from \"./process/scripted_process_runner.js\";\n\nexport { MemoryKeyValueStore } from \"./storage/memory_key_value_store.js\";\nexport { MemoryObjectStore } from \"./storage/memory_object_store.js\";\n\nexport { RecordingEventBus } from \"./events/recording_event_bus.js\";\nexport type { DeliveryMode } from \"./events/recording_event_bus.js\";\n\nexport { NoopLogSink, RecordingLogSink, RecordingTelemetrySink, RecordingTraceSink } from \"./observability/recording_sinks.js\";\n\nexport { runClockContract } from \"./contracts/clock.contract.js\";\nexport { runKeyValueStoreContract } from \"./contracts/key_value_store.contract.js\";\nexport type { KeyValueStoreContractContext } from \"./contracts/key_value_store.contract.js\";\nexport { runObjectStoreContract } from \"./contracts/object_store.contract.js\";\nexport { runEventBusContract, testEvent } from \"./contracts/event_bus.contract.js\";\nexport type { EventBusContractContext, TestEvent } from \"./contracts/event_bus.contract.js\";\n\nexport { LeakDetector } from \"./leak/leak_detector.js\";\nexport type { LeakCheck } from \"./leak/leak_detector.js\";\n","import { Clock, Duration, Instant } from \"@noego/runtime\";\n\n/**\n * Deterministic clock. Time never moves unless the test moves it.\n * Defaults to a fixed instant so snapshots are stable.\n */\nexport class ManualClock extends Clock {\n static readonly defaultStart = Instant.ofEpochMilliseconds(Date.UTC(2020, 0, 1));\n\n private current: Instant;\n\n constructor(start: Instant = ManualClock.defaultStart) {\n super();\n this.current = start;\n }\n\n override now(): Instant {\n return this.current;\n }\n\n set(instant: Instant): void {\n if (instant.isBefore(this.current)) {\n throw new RangeError(`ManualClock cannot move backwards: ${this.current.toString()} -> ${instant.toString()}`);\n }\n this.current = instant;\n }\n\n advanceBy(duration: Duration): void {\n this.current = this.current.plus(duration);\n }\n}\n","import { Duration, Instant, Scheduler, brandId } from \"@noego/runtime\";\nimport type { ScheduleInput, ScheduledTaskHandle, ScheduledTaskId } from \"@noego/runtime\";\nimport type { ManualClock } from \"./manual_clock.js\";\n\nexport interface PendingTask {\n readonly id: ScheduledTaskId;\n readonly label: string;\n readonly deadline: Instant;\n}\n\ninterface InternalTask {\n readonly id: ScheduledTaskId;\n readonly label: string;\n readonly deadline: Instant;\n readonly sequence: number;\n readonly task: () => void | Promise<void>;\n cancelled: boolean;\n}\n\n/**\n * Deterministic scheduler driven by a ManualClock.\n *\n * Locked semantics:\n * - deadlines are (scheduledAt + delay);\n * - equal deadlines run in insertion order;\n * - cancelled tasks never execute;\n * - callbacks scheduled by callbacks are ordered deterministically (insertion\n * sequence is global and monotonic);\n * - async callbacks settle before advancing continues;\n * - runUntilIdle fails with a bounded diagnostic on infinite reschedule loops;\n * - assertNoPendingTasks fails teardown when tasks remain, unless allowed;\n * - CHOSEN MODEL: advancing time RUNS every task whose deadline falls inside\n * the advanced window, in deadline order. advanceBy(Duration.zero) runs\n * tasks already due.\n */\nexport class ManualScheduler extends Scheduler {\n private readonly tasks: InternalTask[] = [];\n private nextSequence = 0;\n private nextId = 0;\n\n constructor(private readonly clock: ManualClock) {\n super();\n }\n\n override schedule(input: ScheduleInput): ScheduledTaskHandle {\n const id = brandId(`task-${++this.nextId}`, \"ScheduledTask\");\n const internal: InternalTask = {\n id,\n label: input.label,\n deadline: this.clock.now().plus(input.delay),\n sequence: this.nextSequence++,\n task: input.task,\n cancelled: false,\n };\n this.tasks.push(internal);\n return {\n id,\n cancel: () => {\n internal.cancelled = true;\n },\n };\n }\n\n pending(): PendingTask[] {\n return this.live()\n .sort(compareTasks)\n .map(({ id, label, deadline }) => ({ id, label, deadline }));\n }\n\n /** Advance the clock, running every task due inside the window in order. */\n async advanceBy(duration: Duration): Promise<void> {\n const target = this.clock.now().plus(duration);\n for (;;) {\n const next = this.nextDue(target);\n if (next === null) break;\n if (next.deadline.isAfter(this.clock.now())) {\n this.clock.set(next.deadline);\n }\n await this.execute(next);\n }\n if (target.isAfter(this.clock.now())) {\n this.clock.set(target);\n }\n }\n\n /** Advance to the earliest pending deadline and run exactly that task. */\n async runNext(): Promise<void> {\n const next = this.live().sort(compareTasks)[0];\n if (next === undefined) {\n throw new Error(\"ManualScheduler.runNext: no pending tasks\");\n }\n if (next.deadline.isAfter(this.clock.now())) {\n this.clock.set(next.deadline);\n }\n await this.execute(next);\n }\n\n /** Run tasks (advancing time as needed) until none remain. */\n async runUntilIdle(options?: { maxTasks?: number }): Promise<void> {\n const maxTasks = options?.maxTasks ?? 10_000;\n let executed = 0;\n while (this.live().length > 0) {\n if (executed >= maxTasks) {\n const labels = this.pending().slice(0, 5).map((t) => t.label).join(\", \");\n throw new Error(\n `ManualScheduler.runUntilIdle: still not idle after ${maxTasks} tasks — probable infinite reschedule loop (next: ${labels})`,\n );\n }\n await this.runNext();\n executed += 1;\n }\n }\n\n /** Teardown assertion: fails when pending tasks remain, unless allowed. */\n assertNoPendingTasks(options?: { allow?: boolean }): void {\n if (options?.allow === true) return;\n const remaining = this.pending();\n if (remaining.length > 0) {\n const labels = remaining.map((t) => `${t.label} @ ${t.deadline.toString()}`).join(\"; \");\n throw new Error(`ManualScheduler: ${remaining.length} pending task(s) at teardown: ${labels}`);\n }\n }\n\n private live(): InternalTask[] {\n // Executed tasks are removed eagerly in execute(), so presence in the\n // list plus not-cancelled means pending.\n return this.tasks.filter((t) => !t.cancelled);\n }\n\n private nextDue(target: Instant): InternalTask | null {\n const candidates = this.live()\n .filter((t) => !t.deadline.isAfter(target))\n .sort(compareTasks);\n return candidates[0] ?? null;\n }\n\n private async execute(task: InternalTask): Promise<void> {\n this.remove(task);\n await task.task();\n }\n\n private remove(task: InternalTask): void {\n const index = this.tasks.indexOf(task);\n this.tasks.splice(index, 1);\n }\n}\n\nfunction compareTasks(a: InternalTask, b: InternalTask): number {\n const byDeadline = a.deadline.compareTo(b.deadline);\n return byDeadline !== 0 ? byDeadline : a.sequence - b.sequence;\n}\n","import { IdGenerator, brandId } from \"@noego/runtime\";\nimport type { BrandedId } from \"@noego/runtime\";\n\n/**\n * Deterministic ID generator: \"<brand>-1\", \"<brand>-2\", … per brand.\n * Fixed IDs keep snapshots stable; isolation makes collisions impossible.\n */\nexport class SequenceIdGenerator extends IdGenerator {\n private readonly counters = new Map<string, number>();\n\n override next<TBrand extends string>(brand: TBrand): BrandedId<TBrand> {\n const current = (this.counters.get(brand) ?? 0) + 1;\n this.counters.set(brand, current);\n return brandId(`${brand.toLowerCase()}-${current}`, brand);\n }\n}\n","import { RandomSource } from \"@noego/runtime\";\n\n/**\n * Deterministic random source (mulberry32). The same seed always yields the\n * same sequence.\n */\nexport class SeededRandomSource extends RandomSource {\n private state: number;\n\n constructor(seed: number = 1) {\n super();\n if (!Number.isInteger(seed)) {\n throw new RangeError(`SeededRandomSource seed must be an integer, got ${seed}`);\n }\n this.state = seed >>> 0;\n }\n\n private nextUint32(): number {\n this.state = (this.state + 0x6d2b79f5) >>> 0;\n let t = this.state;\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n return (t ^ (t >>> 14)) >>> 0;\n }\n\n override bytes(length: number): Uint8Array {\n if (!Number.isInteger(length) || length < 0) {\n throw new RangeError(`bytes length must be a non-negative integer, got ${length}`);\n }\n const out = new Uint8Array(length);\n for (let i = 0; i < length; i += 1) {\n out[i] = this.nextUint32() & 0xff;\n }\n return out;\n }\n\n override integer(minInclusive: number, maxExclusive: number): number {\n if (maxExclusive <= minInclusive) {\n throw new RangeError(`integer range is empty: [${minInclusive}, ${maxExclusive})`);\n }\n const span = maxExclusive - minInclusive;\n return minInclusive + (this.nextUint32() % span);\n }\n}\n","import { FetchClient, FetchError } from \"@noego/runtime\";\nimport type { FetchRequest, FetchResponse } from \"@noego/runtime\";\n\nexport interface FetchScriptEntry {\n /** Human-readable description used in failure diagnostics. */\n readonly describe: string;\n /** Typed predicate the incoming request must satisfy. */\n readonly matches: (request: FetchRequest) => boolean;\n /** Either respond or fail with a normalized FetchError. */\n readonly respond: (request: FetchRequest) => FetchResponse | FetchError;\n}\n\n/**\n * Scripted FetchClient. Scripts are consumed strictly in order; an unexpected\n * or mismatched call fails immediately with a diagnostic.\n */\nexport class ScriptedFetchClient extends FetchClient {\n private readonly script: FetchScriptEntry[];\n private cursor = 0;\n readonly executed: FetchRequest[] = [];\n\n constructor(script: readonly FetchScriptEntry[]) {\n super();\n this.script = [...script];\n }\n\n override execute(request: FetchRequest): Promise<FetchResponse> {\n const entry = this.script[this.cursor];\n if (entry === undefined) {\n return Promise.reject(\n new Error(`ScriptedFetchClient: unexpected call #${this.cursor + 1} (${request.method} ${request.url}); script is exhausted`),\n );\n }\n if (!entry.matches(request)) {\n return Promise.reject(\n new Error(`ScriptedFetchClient: call #${this.cursor + 1} (${request.method} ${request.url}) does not match script entry \"${entry.describe}\"`),\n );\n }\n this.cursor += 1;\n this.executed.push(request);\n const outcome = entry.respond(request);\n return outcome instanceof FetchError ? Promise.reject(outcome) : Promise.resolve(outcome);\n }\n\n /** Teardown assertion: every scripted call must have been consumed. */\n assertScriptConsumed(): void {\n if (this.cursor < this.script.length) {\n const remaining = this.script.slice(this.cursor).map((e) => e.describe).join(\", \");\n throw new Error(`ScriptedFetchClient: ${this.script.length - this.cursor} unconsumed script entr(ies): ${remaining}`);\n }\n }\n}\n","import { ProcessRunner } from \"@noego/runtime\";\nimport type { ProcessCommand, ProcessEvent, ProcessHandle, ProcessResult, SpawnCommand } from \"@noego/runtime\";\n\n/** Spawn handle with test-only deterministic delivery control. */\nexport interface ScriptedSpawnHandle extends ProcessHandle {\n /** Deliver all scripted events now (deterministic, test-controlled). */\n flush(): void;\n}\n\nexport interface ProcessScriptEntry {\n readonly describe: string;\n readonly matches: (command: ProcessCommand) => boolean;\n /** Events emitted (for spawn) / folded into the result (for run). */\n readonly events: readonly ProcessEvent[];\n}\n\n/**\n * Scripted ProcessRunner. Scripts are consumed in order; unexpected commands\n * fail immediately. Spawned handles emit their declared events when\n * flush() is called, keeping delivery under test control.\n */\nexport class ScriptedProcessRunner extends ProcessRunner {\n private readonly script: ProcessScriptEntry[];\n private cursor = 0;\n private readonly openHandles = new Set<ScriptedProcessHandle>();\n\n constructor(script: readonly ProcessScriptEntry[]) {\n super();\n this.script = [...script];\n }\n\n private consume(command: ProcessCommand, kind: string): ProcessScriptEntry {\n const entry = this.script[this.cursor];\n if (entry === undefined) {\n throw new Error(`ScriptedProcessRunner: unexpected ${kind} of \"${command.executable}\"; script is exhausted`);\n }\n if (!entry.matches(command)) {\n throw new Error(`ScriptedProcessRunner: ${kind} of \"${command.executable}\" does not match script entry \"${entry.describe}\"`);\n }\n this.cursor += 1;\n return entry;\n }\n\n override run(command: ProcessCommand): Promise<ProcessResult> {\n try {\n const entry = this.consume(command, \"run\");\n return Promise.resolve(foldEvents(entry.events));\n } catch (error) {\n return Promise.reject(error);\n }\n }\n\n override spawn(command: SpawnCommand): ScriptedSpawnHandle {\n const entry = this.consume(command, \"spawn\");\n const handle = new ScriptedProcessHandle(command.label, entry.events, () => {\n this.openHandles.delete(handle);\n });\n this.openHandles.add(handle);\n return handle;\n }\n\n /** Teardown assertion: no spawned process may remain open. */\n assertNoOpenHandles(): void {\n if (this.openHandles.size > 0) {\n const labels = [...this.openHandles].map((h) => h.label).join(\", \");\n throw new Error(`ScriptedProcessRunner: ${this.openHandles.size} open process handle(s) at teardown: ${labels}`);\n }\n }\n}\n\nclass ScriptedProcessHandle implements ScriptedSpawnHandle {\n private readonly listeners: Array<(event: ProcessEvent) => void> = [];\n private readonly resultPromise: Promise<ProcessResult>;\n private resolveResult!: (result: ProcessResult) => void;\n private flushed = false;\n private killed = false;\n\n constructor(\n public readonly label: string,\n private readonly events: readonly ProcessEvent[],\n private readonly onClosed: () => void,\n ) {\n this.resultPromise = new Promise((resolve) => {\n this.resolveResult = resolve;\n });\n }\n\n onEvent(listener: (event: ProcessEvent) => void): void {\n this.listeners.push(listener);\n }\n\n /** Deliver all scripted events now (deterministic, test-controlled). */\n flush(): void {\n if (this.flushed || this.killed) return;\n this.flushed = true;\n for (const event of this.events) {\n for (const listener of this.listeners) listener(event);\n if (event.kind === \"exit\") {\n this.resolveResult(foldEvents(this.events));\n this.onClosed();\n }\n }\n }\n\n kill(signal: string = \"SIGTERM\"): void {\n if (this.flushed || this.killed) return;\n this.killed = true;\n const exit: ProcessEvent = { kind: \"exit\", exitCode: null, signal };\n for (const listener of this.listeners) listener(exit);\n this.resolveResult({ exitCode: null, signal, stdout: \"\", stderr: \"\" });\n this.onClosed();\n }\n\n wait(): Promise<ProcessResult> {\n return this.resultPromise;\n }\n}\n\nfunction foldEvents(events: readonly ProcessEvent[]): ProcessResult {\n let stdout = \"\";\n let stderr = \"\";\n let exitCode: number | null = null;\n let signal: string | null = null;\n for (const event of events) {\n if (event.kind === \"stdout\") stdout += event.chunk;\n else if (event.kind === \"stderr\") stderr += event.chunk;\n else {\n exitCode = event.exitCode;\n signal = event.signal;\n }\n }\n return { exitCode, signal, stdout, stderr };\n}\n","import { KeyValueStore } from \"@noego/runtime\";\nimport type { Clock, Codec, Instant, Key, PutOptions } from \"@noego/runtime\";\n\ninterface Entry {\n readonly encoded: string;\n readonly expiresAt: Instant | null;\n}\n\n/**\n * Deterministic in-memory KeyValueStore with clock-driven TTL semantics.\n * Passes the same contract as KV/Redis adapters.\n */\nexport class MemoryKeyValueStore extends KeyValueStore {\n private readonly entries = new Map<string, Entry>();\n\n constructor(private readonly clock: Clock) {\n super();\n }\n\n override get<T>(key: Key, codec: Codec<T>): Promise<T | null> {\n const entry = this.entries.get(key);\n if (entry === undefined) return Promise.resolve(null);\n if (entry.expiresAt !== null && !this.clock.now().isBefore(entry.expiresAt)) {\n this.entries.delete(key);\n return Promise.resolve(null);\n }\n return Promise.resolve(codec.decode(entry.encoded));\n }\n\n override put<T>(key: Key, value: T, codec: Codec<T>, options?: PutOptions): Promise<void> {\n const expiresAt = options?.timeToLive !== undefined ? this.clock.now().plus(options.timeToLive) : null;\n this.entries.set(key, { encoded: codec.encode(value), expiresAt });\n return Promise.resolve();\n }\n\n override delete(key: Key): Promise<void> {\n this.entries.delete(key);\n return Promise.resolve();\n }\n}\n","import { ObjectStore, brandId } from \"@noego/runtime\";\nimport type { ObjectKey, ObjectVersion, StoredObject, StoredObjectInput } from \"@noego/runtime\";\n\n/**\n * Deterministic in-memory ObjectStore with monotonically increasing versions.\n * Passes the same contract as R2/S3 adapters.\n */\nexport class MemoryObjectStore extends ObjectStore {\n private readonly objects = new Map<string, StoredObject>();\n private nextVersion = 0;\n\n override read(key: ObjectKey): Promise<StoredObject | null> {\n return Promise.resolve(this.objects.get(key) ?? null);\n }\n\n override write(key: ObjectKey, object: StoredObjectInput): Promise<ObjectVersion> {\n const version = brandId(`v${++this.nextVersion}`, \"ObjectVersion\");\n this.objects.set(key, {\n bytes: object.bytes.slice(),\n contentType: object.contentType,\n metadata: object.metadata !== undefined ? new Map(object.metadata) : undefined,\n version,\n });\n return Promise.resolve(version);\n }\n\n override delete(key: ObjectKey): Promise<void> {\n this.objects.delete(key);\n return Promise.resolve();\n }\n}\n","import { EventBus } from \"@noego/runtime\";\nimport type { DomainEvent, EventHandler, Subscription } from \"@noego/runtime\";\n\nexport type DeliveryMode = \"immediate\" | \"queued\";\n\n/**\n * Recording EventBus. Records exact publication order. Delivery modes:\n * - immediate (default): handlers run during publish;\n * - queued: events buffer until deliverQueued() is called.\n * Active subscriptions at teardown fail assertNoActiveSubscriptions().\n */\nexport class RecordingEventBus<TEvent extends DomainEvent> extends EventBus<TEvent> {\n readonly published: TEvent[] = [];\n private readonly handlers = new Set<EventHandler<TEvent>>();\n private readonly queue: TEvent[] = [];\n\n constructor(private readonly mode: DeliveryMode = \"immediate\") {\n super();\n }\n\n override async publish(event: TEvent): Promise<void> {\n this.published.push(event);\n if (this.mode === \"queued\") {\n this.queue.push(event);\n return;\n }\n await this.deliver(event);\n }\n\n override subscribe(handler: EventHandler<TEvent>): Subscription {\n this.handlers.add(handler);\n const bus = this;\n const subscription = {\n active: true,\n unsubscribe(): void {\n if (!subscription.active) return;\n subscription.active = false;\n bus.handlers.delete(handler);\n },\n };\n return subscription;\n }\n\n /** Deliver buffered events (queued mode) in publication order. */\n async deliverQueued(): Promise<void> {\n while (this.queue.length > 0) {\n const event = this.queue.shift() as TEvent;\n await this.deliver(event);\n }\n }\n\n get activeSubscriptionCount(): number {\n return this.handlers.size;\n }\n\n assertNoActiveSubscriptions(): void {\n if (this.handlers.size > 0) {\n throw new Error(`RecordingEventBus: ${this.handlers.size} active subscription(s) at teardown`);\n }\n }\n\n private async deliver(event: TEvent): Promise<void> {\n for (const handler of [...this.handlers]) {\n await handler(event);\n }\n }\n}\n","import { LogSink, TelemetrySink, TraceSink } from \"@noego/runtime\";\nimport type { LogEnvelope, TelemetryEnvelope, TraceEnvelope } from \"@noego/runtime\";\n\n/** In-memory LogSink recording envelopes in emission order. */\nexport class RecordingLogSink extends LogSink {\n readonly envelopes: LogEnvelope[] = [];\n\n override emit(envelope: LogEnvelope): void {\n this.envelopes.push(envelope);\n }\n}\n\n/** In-memory TraceSink recording envelopes in emission order. */\nexport class RecordingTraceSink extends TraceSink {\n readonly envelopes: TraceEnvelope[] = [];\n\n override emit(envelope: TraceEnvelope): void {\n this.envelopes.push(envelope);\n }\n}\n\n/** In-memory TelemetrySink recording envelopes in emission order. */\nexport class RecordingTelemetrySink extends TelemetrySink {\n readonly envelopes: TelemetryEnvelope[] = [];\n\n override emit(envelope: TelemetryEnvelope): void {\n this.envelopes.push(envelope);\n }\n}\n\n/** LogSink that drops everything (for suites that assert nothing about logs). */\nexport class NoopLogSink extends LogSink {\n override emit(_envelope: LogEnvelope): void {\n /* intentionally empty */\n }\n}\n","import type { Clock } from \"@noego/runtime\";\n\n/**\n * Behavioral contract every Clock implementation must satisfy.\n * Runs inside the caller's jest context.\n */\nexport function runClockContract(name: string, setup: () => { clock: Clock }): void {\n describe(`Clock contract: ${name}`, () => {\n it(\"returns a monotonically non-decreasing instant across consecutive reads\", () => {\n const { clock } = setup();\n const first = clock.now();\n const second = clock.now();\n expect(second.isBefore(first)).toBe(false);\n });\n\n it(\"nowMs agrees with now()\", () => {\n const { clock } = setup();\n const instant = clock.now();\n const ms = clock.nowMs();\n expect(ms).toBeGreaterThanOrEqual(instant.epochMilliseconds);\n });\n });\n}\n","import { Duration, jsonCodec, storageKey } from \"@noego/runtime\";\nimport type { KeyValueStore } from \"@noego/runtime\";\n\nexport interface KeyValueStoreContractContext {\n store: KeyValueStore;\n /** Move the store's time source forward (real adapters may wait/mock). */\n advanceBy: (duration: Duration) => Promise<void>;\n /** Whether the implementation supports TTL expiry. */\n supportsTtl: boolean;\n}\n\n/**\n * Behavioral contract every KeyValueStore implementation must satisfy —\n * memory, KV, and Redis adapters alike.\n */\nexport function runKeyValueStoreContract(name: string, setup: () => KeyValueStoreContractContext): void {\n const codec = jsonCodec<{ n: number }>();\n\n describe(`KeyValueStore contract: ${name}`, () => {\n it(\"returns null for a missing key\", async () => {\n const { store } = setup();\n await expect(store.get(storageKey(\"missing\"), codec)).resolves.toBeNull();\n });\n\n it(\"round-trips a stored value\", async () => {\n const { store } = setup();\n const key = storageKey(\"k1\");\n await store.put(key, { n: 42 }, codec);\n await expect(store.get(key, codec)).resolves.toEqual({ n: 42 });\n });\n\n it(\"overwrites an existing value\", async () => {\n const { store } = setup();\n const key = storageKey(\"k1\");\n await store.put(key, { n: 1 }, codec);\n await store.put(key, { n: 2 }, codec);\n await expect(store.get(key, codec)).resolves.toEqual({ n: 2 });\n });\n\n it(\"deletes a value and tolerates deleting a missing key\", async () => {\n const { store } = setup();\n const key = storageKey(\"k1\");\n await store.put(key, { n: 1 }, codec);\n await store.delete(key);\n await expect(store.get(key, codec)).resolves.toBeNull();\n await expect(store.delete(storageKey(\"missing\"))).resolves.toBeUndefined();\n });\n\n it(\"expires values after their TTL when TTL is supported\", async () => {\n const context = setup();\n if (!context.supportsTtl) return;\n const key = storageKey(\"expiring\");\n await context.store.put(key, { n: 7 }, codec, { timeToLive: Duration.ofSeconds(30) });\n await expect(context.store.get(key, codec)).resolves.toEqual({ n: 7 });\n await context.advanceBy(Duration.ofSeconds(31));\n await expect(context.store.get(key, codec)).resolves.toBeNull();\n });\n\n it(\"keeps values without a TTL alive as time passes\", async () => {\n const context = setup();\n const key = storageKey(\"durable\");\n await context.store.put(key, { n: 9 }, codec);\n await context.advanceBy(Duration.ofMinutes(60));\n await expect(context.store.get(key, codec)).resolves.toEqual({ n: 9 });\n });\n });\n}\n","import { objectKey } from \"@noego/runtime\";\nimport type { ObjectStore } from \"@noego/runtime\";\n\n/**\n * Behavioral contract every ObjectStore implementation must satisfy —\n * memory, R2, S3, and filesystem adapters alike.\n */\nexport function runObjectStoreContract(name: string, setup: () => { store: ObjectStore }): void {\n const bytes = (...values: number[]) => new Uint8Array(values);\n\n describe(`ObjectStore contract: ${name}`, () => {\n it(\"returns null for a missing object\", async () => {\n const { store } = setup();\n await expect(store.read(objectKey(\"missing\"))).resolves.toBeNull();\n });\n\n it(\"round-trips bytes, content type, and metadata\", async () => {\n const { store } = setup();\n const key = objectKey(\"docs/a.bin\");\n await store.write(key, {\n bytes: bytes(1, 2, 3),\n contentType: \"application/octet-stream\",\n metadata: new Map([[\"owner\", \"alice\"]]),\n });\n const stored = await store.read(key);\n expect(stored).not.toBeNull();\n expect([...stored!.bytes]).toEqual([1, 2, 3]);\n expect(stored!.contentType).toBe(\"application/octet-stream\");\n expect(stored!.metadata?.get(\"owner\")).toBe(\"alice\");\n });\n\n it(\"issues a new version on every write\", async () => {\n const { store } = setup();\n const key = objectKey(\"docs/a.bin\");\n const v1 = await store.write(key, { bytes: bytes(1), contentType: \"text/plain\" });\n const v2 = await store.write(key, { bytes: bytes(2), contentType: \"text/plain\" });\n expect(v1).not.toBe(v2);\n const stored = await store.read(key);\n expect(stored!.version).toBe(v2);\n expect([...stored!.bytes]).toEqual([2]);\n });\n\n it(\"deletes an object and tolerates deleting a missing key\", async () => {\n const { store } = setup();\n const key = objectKey(\"docs/a.bin\");\n await store.write(key, { bytes: bytes(1), contentType: \"text/plain\" });\n await store.delete(key);\n await expect(store.read(key)).resolves.toBeNull();\n await expect(store.delete(objectKey(\"missing\"))).resolves.toBeUndefined();\n });\n\n it(\"does not expose caller mutations of written bytes\", async () => {\n const { store } = setup();\n const key = objectKey(\"docs/mutable.bin\");\n const input = bytes(9);\n await store.write(key, { bytes: input, contentType: \"text/plain\" });\n input[0] = 0;\n const stored = await store.read(key);\n expect([...stored!.bytes]).toEqual([9]);\n });\n });\n}\n","import type { DomainEvent, EventBus } from \"@noego/runtime\";\n\nexport interface TestEvent extends DomainEvent {\n readonly type: \"test-event\";\n readonly value: number;\n}\n\nexport const testEvent = (value: number): TestEvent => ({ type: \"test-event\", value });\n\nexport interface EventBusContractContext {\n bus: EventBus<TestEvent>;\n /** Force delivery of any buffered events (no-op for immediate buses). */\n deliver: () => Promise<void>;\n}\n\n/**\n * Behavioral contract every EventBus implementation must satisfy.\n */\nexport function runEventBusContract(name: string, setup: () => EventBusContractContext): void {\n describe(`EventBus contract: ${name}`, () => {\n it(\"delivers published events to subscribers in order\", async () => {\n const { bus, deliver } = setup();\n const seen: number[] = [];\n const subscription = bus.subscribe((event) => {\n seen.push(event.value);\n });\n await bus.publish(testEvent(1));\n await bus.publish(testEvent(2));\n await deliver();\n expect(seen).toEqual([1, 2]);\n subscription.unsubscribe();\n });\n\n it(\"stops delivering after unsubscribe\", async () => {\n const { bus, deliver } = setup();\n const seen: number[] = [];\n const subscription = bus.subscribe((event) => {\n seen.push(event.value);\n });\n await bus.publish(testEvent(1));\n await deliver();\n subscription.unsubscribe();\n await bus.publish(testEvent(2));\n await deliver();\n expect(seen).toEqual([1]);\n });\n\n it(\"supports multiple subscribers\", async () => {\n const { bus, deliver } = setup();\n const a: number[] = [];\n const b: number[] = [];\n const sa = bus.subscribe((event) => {\n a.push(event.value);\n });\n const sb = bus.subscribe((event) => {\n b.push(event.value);\n });\n await bus.publish(testEvent(5));\n await deliver();\n expect(a).toEqual([5]);\n expect(b).toEqual([5]);\n sa.unsubscribe();\n sb.unsubscribe();\n });\n\n it(\"marks subscriptions inactive after unsubscribe and tolerates double unsubscribe\", async () => {\n const { bus, deliver } = setup();\n const seen: number[] = [];\n const subscription = bus.subscribe((event) => {\n seen.push(event.value);\n });\n await bus.publish(testEvent(1));\n await deliver();\n expect(seen).toEqual([1]);\n expect(subscription.active).toBe(true);\n subscription.unsubscribe();\n expect(subscription.active).toBe(false);\n subscription.unsubscribe();\n expect(subscription.active).toBe(false);\n });\n });\n}\n","export interface LeakCheck {\n /** Which resource family this check guards (timers, subscriptions, …). */\n readonly name: string;\n /** Returns a description per leaked resource; empty means clean. */\n readonly check: () => readonly string[];\n}\n\n/**\n * Aggregates leak checks and fails teardown with one clear report listing\n * every leaked resource across all registered checks.\n */\nexport class LeakDetector {\n private readonly checks: LeakCheck[] = [];\n\n register(check: LeakCheck): void {\n this.checks.push(check);\n }\n\n findLeaks(): string[] {\n const leaks: string[] = [];\n for (const { name, check } of this.checks) {\n for (const description of check()) {\n leaks.push(`[${name}] ${description}`);\n }\n }\n return leaks;\n }\n\n assertNoLeaks(): void {\n const leaks = this.findLeaks();\n if (leaks.length > 0) {\n throw new Error(`LeakDetector: ${leaks.length} leaked resource(s):\\n${leaks.join(\"\\n\")}`);\n }\n }\n}\n"],"mappings":"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","names":["import_runtime","import_runtime","import_runtime","import_runtime","import_runtime","import_runtime","import_runtime","import_runtime","import_runtime","import_runtime","import_runtime"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { Clock, Instant, Duration, Scheduler, ScheduleInput, ScheduledTaskHandle, ScheduledTaskId, IdGenerator, BrandedId, RandomSource, FetchRequest, FetchResponse, FetchError, FetchClient, ProcessCommand, ProcessEvent, ProcessRunner, ProcessResult, SpawnCommand, ProcessHandle, KeyValueStore, Key, Codec, PutOptions, ObjectStore, ObjectKey, StoredObject, StoredObjectInput, ObjectVersion, DomainEvent, EventBus, EventHandler, Subscription, LogSink, LogEnvelope, TelemetrySink, TelemetryEnvelope, TraceSink, TraceEnvelope } from '@noego/runtime';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Deterministic clock. Time never moves unless the test moves it.
|
|
5
|
+
* Defaults to a fixed instant so snapshots are stable.
|
|
6
|
+
*/
|
|
7
|
+
declare class ManualClock extends Clock {
|
|
8
|
+
static readonly defaultStart: Instant;
|
|
9
|
+
private current;
|
|
10
|
+
constructor(start?: Instant);
|
|
11
|
+
now(): Instant;
|
|
12
|
+
set(instant: Instant): void;
|
|
13
|
+
advanceBy(duration: Duration): void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface PendingTask {
|
|
17
|
+
readonly id: ScheduledTaskId;
|
|
18
|
+
readonly label: string;
|
|
19
|
+
readonly deadline: Instant;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Deterministic scheduler driven by a ManualClock.
|
|
23
|
+
*
|
|
24
|
+
* Locked semantics:
|
|
25
|
+
* - deadlines are (scheduledAt + delay);
|
|
26
|
+
* - equal deadlines run in insertion order;
|
|
27
|
+
* - cancelled tasks never execute;
|
|
28
|
+
* - callbacks scheduled by callbacks are ordered deterministically (insertion
|
|
29
|
+
* sequence is global and monotonic);
|
|
30
|
+
* - async callbacks settle before advancing continues;
|
|
31
|
+
* - runUntilIdle fails with a bounded diagnostic on infinite reschedule loops;
|
|
32
|
+
* - assertNoPendingTasks fails teardown when tasks remain, unless allowed;
|
|
33
|
+
* - CHOSEN MODEL: advancing time RUNS every task whose deadline falls inside
|
|
34
|
+
* the advanced window, in deadline order. advanceBy(Duration.zero) runs
|
|
35
|
+
* tasks already due.
|
|
36
|
+
*/
|
|
37
|
+
declare class ManualScheduler extends Scheduler {
|
|
38
|
+
private readonly clock;
|
|
39
|
+
private readonly tasks;
|
|
40
|
+
private nextSequence;
|
|
41
|
+
private nextId;
|
|
42
|
+
constructor(clock: ManualClock);
|
|
43
|
+
schedule(input: ScheduleInput): ScheduledTaskHandle;
|
|
44
|
+
pending(): PendingTask[];
|
|
45
|
+
/** Advance the clock, running every task due inside the window in order. */
|
|
46
|
+
advanceBy(duration: Duration): Promise<void>;
|
|
47
|
+
/** Advance to the earliest pending deadline and run exactly that task. */
|
|
48
|
+
runNext(): Promise<void>;
|
|
49
|
+
/** Run tasks (advancing time as needed) until none remain. */
|
|
50
|
+
runUntilIdle(options?: {
|
|
51
|
+
maxTasks?: number;
|
|
52
|
+
}): Promise<void>;
|
|
53
|
+
/** Teardown assertion: fails when pending tasks remain, unless allowed. */
|
|
54
|
+
assertNoPendingTasks(options?: {
|
|
55
|
+
allow?: boolean;
|
|
56
|
+
}): void;
|
|
57
|
+
private live;
|
|
58
|
+
private nextDue;
|
|
59
|
+
private execute;
|
|
60
|
+
private remove;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Deterministic ID generator: "<brand>-1", "<brand>-2", … per brand.
|
|
65
|
+
* Fixed IDs keep snapshots stable; isolation makes collisions impossible.
|
|
66
|
+
*/
|
|
67
|
+
declare class SequenceIdGenerator extends IdGenerator {
|
|
68
|
+
private readonly counters;
|
|
69
|
+
next<TBrand extends string>(brand: TBrand): BrandedId<TBrand>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Deterministic random source (mulberry32). The same seed always yields the
|
|
74
|
+
* same sequence.
|
|
75
|
+
*/
|
|
76
|
+
declare class SeededRandomSource extends RandomSource {
|
|
77
|
+
private state;
|
|
78
|
+
constructor(seed?: number);
|
|
79
|
+
private nextUint32;
|
|
80
|
+
bytes(length: number): Uint8Array;
|
|
81
|
+
integer(minInclusive: number, maxExclusive: number): number;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface FetchScriptEntry {
|
|
85
|
+
/** Human-readable description used in failure diagnostics. */
|
|
86
|
+
readonly describe: string;
|
|
87
|
+
/** Typed predicate the incoming request must satisfy. */
|
|
88
|
+
readonly matches: (request: FetchRequest) => boolean;
|
|
89
|
+
/** Either respond or fail with a normalized FetchError. */
|
|
90
|
+
readonly respond: (request: FetchRequest) => FetchResponse | FetchError;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Scripted FetchClient. Scripts are consumed strictly in order; an unexpected
|
|
94
|
+
* or mismatched call fails immediately with a diagnostic.
|
|
95
|
+
*/
|
|
96
|
+
declare class ScriptedFetchClient extends FetchClient {
|
|
97
|
+
private readonly script;
|
|
98
|
+
private cursor;
|
|
99
|
+
readonly executed: FetchRequest[];
|
|
100
|
+
constructor(script: readonly FetchScriptEntry[]);
|
|
101
|
+
execute(request: FetchRequest): Promise<FetchResponse>;
|
|
102
|
+
/** Teardown assertion: every scripted call must have been consumed. */
|
|
103
|
+
assertScriptConsumed(): void;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Spawn handle with test-only deterministic delivery control. */
|
|
107
|
+
interface ScriptedSpawnHandle extends ProcessHandle {
|
|
108
|
+
/** Deliver all scripted events now (deterministic, test-controlled). */
|
|
109
|
+
flush(): void;
|
|
110
|
+
}
|
|
111
|
+
interface ProcessScriptEntry {
|
|
112
|
+
readonly describe: string;
|
|
113
|
+
readonly matches: (command: ProcessCommand) => boolean;
|
|
114
|
+
/** Events emitted (for spawn) / folded into the result (for run). */
|
|
115
|
+
readonly events: readonly ProcessEvent[];
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Scripted ProcessRunner. Scripts are consumed in order; unexpected commands
|
|
119
|
+
* fail immediately. Spawned handles emit their declared events when
|
|
120
|
+
* flush() is called, keeping delivery under test control.
|
|
121
|
+
*/
|
|
122
|
+
declare class ScriptedProcessRunner extends ProcessRunner {
|
|
123
|
+
private readonly script;
|
|
124
|
+
private cursor;
|
|
125
|
+
private readonly openHandles;
|
|
126
|
+
constructor(script: readonly ProcessScriptEntry[]);
|
|
127
|
+
private consume;
|
|
128
|
+
run(command: ProcessCommand): Promise<ProcessResult>;
|
|
129
|
+
spawn(command: SpawnCommand): ScriptedSpawnHandle;
|
|
130
|
+
/** Teardown assertion: no spawned process may remain open. */
|
|
131
|
+
assertNoOpenHandles(): void;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Deterministic in-memory KeyValueStore with clock-driven TTL semantics.
|
|
136
|
+
* Passes the same contract as KV/Redis adapters.
|
|
137
|
+
*/
|
|
138
|
+
declare class MemoryKeyValueStore extends KeyValueStore {
|
|
139
|
+
private readonly clock;
|
|
140
|
+
private readonly entries;
|
|
141
|
+
constructor(clock: Clock);
|
|
142
|
+
get<T>(key: Key, codec: Codec<T>): Promise<T | null>;
|
|
143
|
+
put<T>(key: Key, value: T, codec: Codec<T>, options?: PutOptions): Promise<void>;
|
|
144
|
+
delete(key: Key): Promise<void>;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Deterministic in-memory ObjectStore with monotonically increasing versions.
|
|
149
|
+
* Passes the same contract as R2/S3 adapters.
|
|
150
|
+
*/
|
|
151
|
+
declare class MemoryObjectStore extends ObjectStore {
|
|
152
|
+
private readonly objects;
|
|
153
|
+
private nextVersion;
|
|
154
|
+
read(key: ObjectKey): Promise<StoredObject | null>;
|
|
155
|
+
write(key: ObjectKey, object: StoredObjectInput): Promise<ObjectVersion>;
|
|
156
|
+
delete(key: ObjectKey): Promise<void>;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
type DeliveryMode = "immediate" | "queued";
|
|
160
|
+
/**
|
|
161
|
+
* Recording EventBus. Records exact publication order. Delivery modes:
|
|
162
|
+
* - immediate (default): handlers run during publish;
|
|
163
|
+
* - queued: events buffer until deliverQueued() is called.
|
|
164
|
+
* Active subscriptions at teardown fail assertNoActiveSubscriptions().
|
|
165
|
+
*/
|
|
166
|
+
declare class RecordingEventBus<TEvent extends DomainEvent> extends EventBus<TEvent> {
|
|
167
|
+
private readonly mode;
|
|
168
|
+
readonly published: TEvent[];
|
|
169
|
+
private readonly handlers;
|
|
170
|
+
private readonly queue;
|
|
171
|
+
constructor(mode?: DeliveryMode);
|
|
172
|
+
publish(event: TEvent): Promise<void>;
|
|
173
|
+
subscribe(handler: EventHandler<TEvent>): Subscription;
|
|
174
|
+
/** Deliver buffered events (queued mode) in publication order. */
|
|
175
|
+
deliverQueued(): Promise<void>;
|
|
176
|
+
get activeSubscriptionCount(): number;
|
|
177
|
+
assertNoActiveSubscriptions(): void;
|
|
178
|
+
private deliver;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** In-memory LogSink recording envelopes in emission order. */
|
|
182
|
+
declare class RecordingLogSink extends LogSink {
|
|
183
|
+
readonly envelopes: LogEnvelope[];
|
|
184
|
+
emit(envelope: LogEnvelope): void;
|
|
185
|
+
}
|
|
186
|
+
/** In-memory TraceSink recording envelopes in emission order. */
|
|
187
|
+
declare class RecordingTraceSink extends TraceSink {
|
|
188
|
+
readonly envelopes: TraceEnvelope[];
|
|
189
|
+
emit(envelope: TraceEnvelope): void;
|
|
190
|
+
}
|
|
191
|
+
/** In-memory TelemetrySink recording envelopes in emission order. */
|
|
192
|
+
declare class RecordingTelemetrySink extends TelemetrySink {
|
|
193
|
+
readonly envelopes: TelemetryEnvelope[];
|
|
194
|
+
emit(envelope: TelemetryEnvelope): void;
|
|
195
|
+
}
|
|
196
|
+
/** LogSink that drops everything (for suites that assert nothing about logs). */
|
|
197
|
+
declare class NoopLogSink extends LogSink {
|
|
198
|
+
emit(_envelope: LogEnvelope): void;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Behavioral contract every Clock implementation must satisfy.
|
|
203
|
+
* Runs inside the caller's jest context.
|
|
204
|
+
*/
|
|
205
|
+
declare function runClockContract(name: string, setup: () => {
|
|
206
|
+
clock: Clock;
|
|
207
|
+
}): void;
|
|
208
|
+
|
|
209
|
+
interface KeyValueStoreContractContext {
|
|
210
|
+
store: KeyValueStore;
|
|
211
|
+
/** Move the store's time source forward (real adapters may wait/mock). */
|
|
212
|
+
advanceBy: (duration: Duration) => Promise<void>;
|
|
213
|
+
/** Whether the implementation supports TTL expiry. */
|
|
214
|
+
supportsTtl: boolean;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Behavioral contract every KeyValueStore implementation must satisfy —
|
|
218
|
+
* memory, KV, and Redis adapters alike.
|
|
219
|
+
*/
|
|
220
|
+
declare function runKeyValueStoreContract(name: string, setup: () => KeyValueStoreContractContext): void;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Behavioral contract every ObjectStore implementation must satisfy —
|
|
224
|
+
* memory, R2, S3, and filesystem adapters alike.
|
|
225
|
+
*/
|
|
226
|
+
declare function runObjectStoreContract(name: string, setup: () => {
|
|
227
|
+
store: ObjectStore;
|
|
228
|
+
}): void;
|
|
229
|
+
|
|
230
|
+
interface TestEvent extends DomainEvent {
|
|
231
|
+
readonly type: "test-event";
|
|
232
|
+
readonly value: number;
|
|
233
|
+
}
|
|
234
|
+
declare const testEvent: (value: number) => TestEvent;
|
|
235
|
+
interface EventBusContractContext {
|
|
236
|
+
bus: EventBus<TestEvent>;
|
|
237
|
+
/** Force delivery of any buffered events (no-op for immediate buses). */
|
|
238
|
+
deliver: () => Promise<void>;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Behavioral contract every EventBus implementation must satisfy.
|
|
242
|
+
*/
|
|
243
|
+
declare function runEventBusContract(name: string, setup: () => EventBusContractContext): void;
|
|
244
|
+
|
|
245
|
+
interface LeakCheck {
|
|
246
|
+
/** Which resource family this check guards (timers, subscriptions, …). */
|
|
247
|
+
readonly name: string;
|
|
248
|
+
/** Returns a description per leaked resource; empty means clean. */
|
|
249
|
+
readonly check: () => readonly string[];
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Aggregates leak checks and fails teardown with one clear report listing
|
|
253
|
+
* every leaked resource across all registered checks.
|
|
254
|
+
*/
|
|
255
|
+
declare class LeakDetector {
|
|
256
|
+
private readonly checks;
|
|
257
|
+
register(check: LeakCheck): void;
|
|
258
|
+
findLeaks(): string[];
|
|
259
|
+
assertNoLeaks(): void;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export { type DeliveryMode, type EventBusContractContext, type FetchScriptEntry, type KeyValueStoreContractContext, type LeakCheck, LeakDetector, ManualClock, ManualScheduler, MemoryKeyValueStore, MemoryObjectStore, NoopLogSink, type PendingTask, type ProcessScriptEntry, RecordingEventBus, RecordingLogSink, RecordingTelemetrySink, RecordingTraceSink, ScriptedFetchClient, ScriptedProcessRunner, type ScriptedSpawnHandle, SeededRandomSource, SequenceIdGenerator, type TestEvent, runClockContract, runEventBusContract, runKeyValueStoreContract, runObjectStoreContract, testEvent };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { Clock, Instant, Duration, Scheduler, ScheduleInput, ScheduledTaskHandle, ScheduledTaskId, IdGenerator, BrandedId, RandomSource, FetchRequest, FetchResponse, FetchError, FetchClient, ProcessCommand, ProcessEvent, ProcessRunner, ProcessResult, SpawnCommand, ProcessHandle, KeyValueStore, Key, Codec, PutOptions, ObjectStore, ObjectKey, StoredObject, StoredObjectInput, ObjectVersion, DomainEvent, EventBus, EventHandler, Subscription, LogSink, LogEnvelope, TelemetrySink, TelemetryEnvelope, TraceSink, TraceEnvelope } from '@noego/runtime';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Deterministic clock. Time never moves unless the test moves it.
|
|
5
|
+
* Defaults to a fixed instant so snapshots are stable.
|
|
6
|
+
*/
|
|
7
|
+
declare class ManualClock extends Clock {
|
|
8
|
+
static readonly defaultStart: Instant;
|
|
9
|
+
private current;
|
|
10
|
+
constructor(start?: Instant);
|
|
11
|
+
now(): Instant;
|
|
12
|
+
set(instant: Instant): void;
|
|
13
|
+
advanceBy(duration: Duration): void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface PendingTask {
|
|
17
|
+
readonly id: ScheduledTaskId;
|
|
18
|
+
readonly label: string;
|
|
19
|
+
readonly deadline: Instant;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Deterministic scheduler driven by a ManualClock.
|
|
23
|
+
*
|
|
24
|
+
* Locked semantics:
|
|
25
|
+
* - deadlines are (scheduledAt + delay);
|
|
26
|
+
* - equal deadlines run in insertion order;
|
|
27
|
+
* - cancelled tasks never execute;
|
|
28
|
+
* - callbacks scheduled by callbacks are ordered deterministically (insertion
|
|
29
|
+
* sequence is global and monotonic);
|
|
30
|
+
* - async callbacks settle before advancing continues;
|
|
31
|
+
* - runUntilIdle fails with a bounded diagnostic on infinite reschedule loops;
|
|
32
|
+
* - assertNoPendingTasks fails teardown when tasks remain, unless allowed;
|
|
33
|
+
* - CHOSEN MODEL: advancing time RUNS every task whose deadline falls inside
|
|
34
|
+
* the advanced window, in deadline order. advanceBy(Duration.zero) runs
|
|
35
|
+
* tasks already due.
|
|
36
|
+
*/
|
|
37
|
+
declare class ManualScheduler extends Scheduler {
|
|
38
|
+
private readonly clock;
|
|
39
|
+
private readonly tasks;
|
|
40
|
+
private nextSequence;
|
|
41
|
+
private nextId;
|
|
42
|
+
constructor(clock: ManualClock);
|
|
43
|
+
schedule(input: ScheduleInput): ScheduledTaskHandle;
|
|
44
|
+
pending(): PendingTask[];
|
|
45
|
+
/** Advance the clock, running every task due inside the window in order. */
|
|
46
|
+
advanceBy(duration: Duration): Promise<void>;
|
|
47
|
+
/** Advance to the earliest pending deadline and run exactly that task. */
|
|
48
|
+
runNext(): Promise<void>;
|
|
49
|
+
/** Run tasks (advancing time as needed) until none remain. */
|
|
50
|
+
runUntilIdle(options?: {
|
|
51
|
+
maxTasks?: number;
|
|
52
|
+
}): Promise<void>;
|
|
53
|
+
/** Teardown assertion: fails when pending tasks remain, unless allowed. */
|
|
54
|
+
assertNoPendingTasks(options?: {
|
|
55
|
+
allow?: boolean;
|
|
56
|
+
}): void;
|
|
57
|
+
private live;
|
|
58
|
+
private nextDue;
|
|
59
|
+
private execute;
|
|
60
|
+
private remove;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Deterministic ID generator: "<brand>-1", "<brand>-2", … per brand.
|
|
65
|
+
* Fixed IDs keep snapshots stable; isolation makes collisions impossible.
|
|
66
|
+
*/
|
|
67
|
+
declare class SequenceIdGenerator extends IdGenerator {
|
|
68
|
+
private readonly counters;
|
|
69
|
+
next<TBrand extends string>(brand: TBrand): BrandedId<TBrand>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Deterministic random source (mulberry32). The same seed always yields the
|
|
74
|
+
* same sequence.
|
|
75
|
+
*/
|
|
76
|
+
declare class SeededRandomSource extends RandomSource {
|
|
77
|
+
private state;
|
|
78
|
+
constructor(seed?: number);
|
|
79
|
+
private nextUint32;
|
|
80
|
+
bytes(length: number): Uint8Array;
|
|
81
|
+
integer(minInclusive: number, maxExclusive: number): number;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface FetchScriptEntry {
|
|
85
|
+
/** Human-readable description used in failure diagnostics. */
|
|
86
|
+
readonly describe: string;
|
|
87
|
+
/** Typed predicate the incoming request must satisfy. */
|
|
88
|
+
readonly matches: (request: FetchRequest) => boolean;
|
|
89
|
+
/** Either respond or fail with a normalized FetchError. */
|
|
90
|
+
readonly respond: (request: FetchRequest) => FetchResponse | FetchError;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Scripted FetchClient. Scripts are consumed strictly in order; an unexpected
|
|
94
|
+
* or mismatched call fails immediately with a diagnostic.
|
|
95
|
+
*/
|
|
96
|
+
declare class ScriptedFetchClient extends FetchClient {
|
|
97
|
+
private readonly script;
|
|
98
|
+
private cursor;
|
|
99
|
+
readonly executed: FetchRequest[];
|
|
100
|
+
constructor(script: readonly FetchScriptEntry[]);
|
|
101
|
+
execute(request: FetchRequest): Promise<FetchResponse>;
|
|
102
|
+
/** Teardown assertion: every scripted call must have been consumed. */
|
|
103
|
+
assertScriptConsumed(): void;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Spawn handle with test-only deterministic delivery control. */
|
|
107
|
+
interface ScriptedSpawnHandle extends ProcessHandle {
|
|
108
|
+
/** Deliver all scripted events now (deterministic, test-controlled). */
|
|
109
|
+
flush(): void;
|
|
110
|
+
}
|
|
111
|
+
interface ProcessScriptEntry {
|
|
112
|
+
readonly describe: string;
|
|
113
|
+
readonly matches: (command: ProcessCommand) => boolean;
|
|
114
|
+
/** Events emitted (for spawn) / folded into the result (for run). */
|
|
115
|
+
readonly events: readonly ProcessEvent[];
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Scripted ProcessRunner. Scripts are consumed in order; unexpected commands
|
|
119
|
+
* fail immediately. Spawned handles emit their declared events when
|
|
120
|
+
* flush() is called, keeping delivery under test control.
|
|
121
|
+
*/
|
|
122
|
+
declare class ScriptedProcessRunner extends ProcessRunner {
|
|
123
|
+
private readonly script;
|
|
124
|
+
private cursor;
|
|
125
|
+
private readonly openHandles;
|
|
126
|
+
constructor(script: readonly ProcessScriptEntry[]);
|
|
127
|
+
private consume;
|
|
128
|
+
run(command: ProcessCommand): Promise<ProcessResult>;
|
|
129
|
+
spawn(command: SpawnCommand): ScriptedSpawnHandle;
|
|
130
|
+
/** Teardown assertion: no spawned process may remain open. */
|
|
131
|
+
assertNoOpenHandles(): void;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Deterministic in-memory KeyValueStore with clock-driven TTL semantics.
|
|
136
|
+
* Passes the same contract as KV/Redis adapters.
|
|
137
|
+
*/
|
|
138
|
+
declare class MemoryKeyValueStore extends KeyValueStore {
|
|
139
|
+
private readonly clock;
|
|
140
|
+
private readonly entries;
|
|
141
|
+
constructor(clock: Clock);
|
|
142
|
+
get<T>(key: Key, codec: Codec<T>): Promise<T | null>;
|
|
143
|
+
put<T>(key: Key, value: T, codec: Codec<T>, options?: PutOptions): Promise<void>;
|
|
144
|
+
delete(key: Key): Promise<void>;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Deterministic in-memory ObjectStore with monotonically increasing versions.
|
|
149
|
+
* Passes the same contract as R2/S3 adapters.
|
|
150
|
+
*/
|
|
151
|
+
declare class MemoryObjectStore extends ObjectStore {
|
|
152
|
+
private readonly objects;
|
|
153
|
+
private nextVersion;
|
|
154
|
+
read(key: ObjectKey): Promise<StoredObject | null>;
|
|
155
|
+
write(key: ObjectKey, object: StoredObjectInput): Promise<ObjectVersion>;
|
|
156
|
+
delete(key: ObjectKey): Promise<void>;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
type DeliveryMode = "immediate" | "queued";
|
|
160
|
+
/**
|
|
161
|
+
* Recording EventBus. Records exact publication order. Delivery modes:
|
|
162
|
+
* - immediate (default): handlers run during publish;
|
|
163
|
+
* - queued: events buffer until deliverQueued() is called.
|
|
164
|
+
* Active subscriptions at teardown fail assertNoActiveSubscriptions().
|
|
165
|
+
*/
|
|
166
|
+
declare class RecordingEventBus<TEvent extends DomainEvent> extends EventBus<TEvent> {
|
|
167
|
+
private readonly mode;
|
|
168
|
+
readonly published: TEvent[];
|
|
169
|
+
private readonly handlers;
|
|
170
|
+
private readonly queue;
|
|
171
|
+
constructor(mode?: DeliveryMode);
|
|
172
|
+
publish(event: TEvent): Promise<void>;
|
|
173
|
+
subscribe(handler: EventHandler<TEvent>): Subscription;
|
|
174
|
+
/** Deliver buffered events (queued mode) in publication order. */
|
|
175
|
+
deliverQueued(): Promise<void>;
|
|
176
|
+
get activeSubscriptionCount(): number;
|
|
177
|
+
assertNoActiveSubscriptions(): void;
|
|
178
|
+
private deliver;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** In-memory LogSink recording envelopes in emission order. */
|
|
182
|
+
declare class RecordingLogSink extends LogSink {
|
|
183
|
+
readonly envelopes: LogEnvelope[];
|
|
184
|
+
emit(envelope: LogEnvelope): void;
|
|
185
|
+
}
|
|
186
|
+
/** In-memory TraceSink recording envelopes in emission order. */
|
|
187
|
+
declare class RecordingTraceSink extends TraceSink {
|
|
188
|
+
readonly envelopes: TraceEnvelope[];
|
|
189
|
+
emit(envelope: TraceEnvelope): void;
|
|
190
|
+
}
|
|
191
|
+
/** In-memory TelemetrySink recording envelopes in emission order. */
|
|
192
|
+
declare class RecordingTelemetrySink extends TelemetrySink {
|
|
193
|
+
readonly envelopes: TelemetryEnvelope[];
|
|
194
|
+
emit(envelope: TelemetryEnvelope): void;
|
|
195
|
+
}
|
|
196
|
+
/** LogSink that drops everything (for suites that assert nothing about logs). */
|
|
197
|
+
declare class NoopLogSink extends LogSink {
|
|
198
|
+
emit(_envelope: LogEnvelope): void;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Behavioral contract every Clock implementation must satisfy.
|
|
203
|
+
* Runs inside the caller's jest context.
|
|
204
|
+
*/
|
|
205
|
+
declare function runClockContract(name: string, setup: () => {
|
|
206
|
+
clock: Clock;
|
|
207
|
+
}): void;
|
|
208
|
+
|
|
209
|
+
interface KeyValueStoreContractContext {
|
|
210
|
+
store: KeyValueStore;
|
|
211
|
+
/** Move the store's time source forward (real adapters may wait/mock). */
|
|
212
|
+
advanceBy: (duration: Duration) => Promise<void>;
|
|
213
|
+
/** Whether the implementation supports TTL expiry. */
|
|
214
|
+
supportsTtl: boolean;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Behavioral contract every KeyValueStore implementation must satisfy —
|
|
218
|
+
* memory, KV, and Redis adapters alike.
|
|
219
|
+
*/
|
|
220
|
+
declare function runKeyValueStoreContract(name: string, setup: () => KeyValueStoreContractContext): void;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Behavioral contract every ObjectStore implementation must satisfy —
|
|
224
|
+
* memory, R2, S3, and filesystem adapters alike.
|
|
225
|
+
*/
|
|
226
|
+
declare function runObjectStoreContract(name: string, setup: () => {
|
|
227
|
+
store: ObjectStore;
|
|
228
|
+
}): void;
|
|
229
|
+
|
|
230
|
+
interface TestEvent extends DomainEvent {
|
|
231
|
+
readonly type: "test-event";
|
|
232
|
+
readonly value: number;
|
|
233
|
+
}
|
|
234
|
+
declare const testEvent: (value: number) => TestEvent;
|
|
235
|
+
interface EventBusContractContext {
|
|
236
|
+
bus: EventBus<TestEvent>;
|
|
237
|
+
/** Force delivery of any buffered events (no-op for immediate buses). */
|
|
238
|
+
deliver: () => Promise<void>;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Behavioral contract every EventBus implementation must satisfy.
|
|
242
|
+
*/
|
|
243
|
+
declare function runEventBusContract(name: string, setup: () => EventBusContractContext): void;
|
|
244
|
+
|
|
245
|
+
interface LeakCheck {
|
|
246
|
+
/** Which resource family this check guards (timers, subscriptions, …). */
|
|
247
|
+
readonly name: string;
|
|
248
|
+
/** Returns a description per leaked resource; empty means clean. */
|
|
249
|
+
readonly check: () => readonly string[];
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Aggregates leak checks and fails teardown with one clear report listing
|
|
253
|
+
* every leaked resource across all registered checks.
|
|
254
|
+
*/
|
|
255
|
+
declare class LeakDetector {
|
|
256
|
+
private readonly checks;
|
|
257
|
+
register(check: LeakCheck): void;
|
|
258
|
+
findLeaks(): string[];
|
|
259
|
+
assertNoLeaks(): void;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export { type DeliveryMode, type EventBusContractContext, type FetchScriptEntry, type KeyValueStoreContractContext, type LeakCheck, LeakDetector, ManualClock, ManualScheduler, MemoryKeyValueStore, MemoryObjectStore, NoopLogSink, type PendingTask, type ProcessScriptEntry, RecordingEventBus, RecordingLogSink, RecordingTelemetrySink, RecordingTraceSink, ScriptedFetchClient, ScriptedProcessRunner, type ScriptedSpawnHandle, SeededRandomSource, SequenceIdGenerator, type TestEvent, runClockContract, runEventBusContract, runKeyValueStoreContract, runObjectStoreContract, testEvent };
|