@nifrajs/testing 1.11.0 → 1.12.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.
@@ -0,0 +1,669 @@
1
+ /**
2
+ * Profile-based adapter certification. Profiles are structural and dependency-free: an adapter package
3
+ * uses this only in its test/CI surface, while the resulting capability matrix is portable JSON evidence.
4
+ */
5
+
6
+ export interface CertificationContext {
7
+ /** Deterministic per-check key safe for shared test backends and stable re-runs. */
8
+ readonly key: (suffix: string) => string
9
+ }
10
+
11
+ export interface CertificationCheck<Adapter> {
12
+ readonly id: string
13
+ readonly capability: string
14
+ readonly run: (adapter: Adapter, context: CertificationContext) => void | Promise<void>
15
+ }
16
+
17
+ export interface AdapterCertificationProfile<Adapter> {
18
+ readonly id: string
19
+ readonly version: number
20
+ readonly capabilities: readonly string[]
21
+ readonly checks: readonly CertificationCheck<Adapter>[]
22
+ }
23
+
24
+ export interface CertificationCheckEvidence {
25
+ readonly id: string
26
+ readonly capability: string
27
+ readonly ok: boolean
28
+ /** Error class only. Messages may contain credentials/provider payloads and are never evidence. */
29
+ readonly errorName?: string
30
+ }
31
+
32
+ export interface CertificationCapabilityEvidence {
33
+ readonly capability: string
34
+ readonly status: "passed" | "failed"
35
+ readonly checks: readonly string[]
36
+ }
37
+
38
+ export interface AdapterCertificationReport {
39
+ readonly schemaVersion: 1
40
+ readonly ok: boolean
41
+ readonly profile: { readonly id: string; readonly version: number }
42
+ readonly adapterId: string
43
+ readonly checks: readonly CertificationCheckEvidence[]
44
+ readonly capabilities: readonly CertificationCapabilityEvidence[]
45
+ readonly evidenceHash: string
46
+ }
47
+
48
+ export class AdapterCertificationError extends Error {
49
+ constructor(readonly report: AdapterCertificationReport) {
50
+ const failed = report.checks.filter((check) => !check.ok).map((check) => check.id)
51
+ super(`adapter ${report.adapterId} failed ${report.profile.id}: ${failed.join(", ")}`)
52
+ this.name = "AdapterCertificationError"
53
+ }
54
+ }
55
+
56
+ const TOKEN = /^[a-z0-9][a-z0-9._:-]{0,127}$/
57
+ const MAX_CHECKS = 128
58
+
59
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
60
+ typeof value === "object" && value !== null && !Array.isArray(value)
61
+
62
+ function canonical(value: unknown): string {
63
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`
64
+ if (isRecord(value))
65
+ return `{${Object.keys(value)
66
+ .sort()
67
+ .map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
68
+ .join(",")}}`
69
+ return JSON.stringify(value)
70
+ }
71
+
72
+ async function sha256(value: string): Promise<string> {
73
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value))
74
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")
75
+ }
76
+
77
+ function validateProfile<Adapter>(profile: AdapterCertificationProfile<Adapter>): void {
78
+ if (!TOKEN.test(profile.id))
79
+ throw new TypeError("certification profile id must be a bounded token")
80
+ if (!Number.isInteger(profile.version) || profile.version < 1)
81
+ throw new TypeError("certification profile version must be a positive integer")
82
+ if (profile.checks.length < 1 || profile.checks.length > MAX_CHECKS)
83
+ throw new RangeError(`certification profile must contain 1-${MAX_CHECKS} checks`)
84
+ const capabilities = new Set(profile.capabilities)
85
+ if (capabilities.size !== profile.capabilities.length)
86
+ throw new TypeError("certification profile capability names must be unique")
87
+ const ids = new Set<string>()
88
+ for (const capability of capabilities) {
89
+ if (!TOKEN.test(capability))
90
+ throw new TypeError("certification capability must be a bounded token")
91
+ }
92
+ for (const check of profile.checks) {
93
+ if (!TOKEN.test(check.id) || ids.has(check.id))
94
+ throw new TypeError("certification check ids must be unique bounded tokens")
95
+ if (!capabilities.has(check.capability))
96
+ throw new TypeError(
97
+ `certification check ${check.id} names undeclared capability ${check.capability}`,
98
+ )
99
+ ids.add(check.id)
100
+ }
101
+ for (const capability of capabilities) {
102
+ if (!profile.checks.some((check) => check.capability === capability))
103
+ throw new TypeError(`certification capability has no checks: ${capability}`)
104
+ }
105
+ }
106
+
107
+ /** Define and validate a custom domain/provider profile at module initialization. */
108
+ export function defineCertificationProfile<Adapter>(
109
+ profile: AdapterCertificationProfile<Adapter>,
110
+ ): AdapterCertificationProfile<Adapter> {
111
+ validateProfile(profile)
112
+ return profile
113
+ }
114
+
115
+ export async function certifyAdapter<Adapter>(options: {
116
+ readonly profile: AdapterCertificationProfile<Adapter>
117
+ readonly adapterId: string
118
+ /** A fresh adapter per check prevents one failed check from contaminating the next. */
119
+ readonly createAdapter: () => Adapter | Promise<Adapter>
120
+ readonly cleanup?: (adapter: Adapter) => void | Promise<void>
121
+ }): Promise<AdapterCertificationReport> {
122
+ validateProfile(options.profile)
123
+ if (!TOKEN.test(options.adapterId)) throw new TypeError("adapter id must be a bounded token")
124
+ const checks: CertificationCheckEvidence[] = []
125
+ for (const check of options.profile.checks) {
126
+ let adapter: Adapter | undefined
127
+ try {
128
+ adapter = await options.createAdapter()
129
+ await check.run(adapter, {
130
+ key: (suffix) =>
131
+ `nifra-cert:${options.profile.id}:${options.adapterId}:${check.id}:${suffix}`,
132
+ })
133
+ checks.push({ id: check.id, capability: check.capability, ok: true })
134
+ } catch (error) {
135
+ checks.push({
136
+ id: check.id,
137
+ capability: check.capability,
138
+ ok: false,
139
+ errorName:
140
+ error instanceof Error && TOKEN.test(error.name.toLowerCase())
141
+ ? error.name
142
+ : "AdapterError",
143
+ })
144
+ } finally {
145
+ if (adapter !== undefined && options.cleanup !== undefined) {
146
+ try {
147
+ await options.cleanup(adapter)
148
+ } catch {
149
+ // Cleanup cannot turn a failed functional check into success, and provider messages are not
150
+ // persisted. Adapter suites should make cleanup idempotent and observable separately.
151
+ }
152
+ }
153
+ }
154
+ }
155
+ const capabilities = options.profile.capabilities.map((capability) => {
156
+ const ids = checks.filter((check) => check.capability === capability)
157
+ return {
158
+ capability,
159
+ status: ids.every((check) => check.ok) ? ("passed" as const) : ("failed" as const),
160
+ checks: ids.map((check) => check.id),
161
+ }
162
+ })
163
+ const body = {
164
+ schemaVersion: 1 as const,
165
+ ok: checks.every((check) => check.ok),
166
+ profile: { id: options.profile.id, version: options.profile.version },
167
+ adapterId: options.adapterId,
168
+ checks,
169
+ capabilities,
170
+ }
171
+ return { ...body, evidenceHash: await sha256(canonical(body)) }
172
+ }
173
+
174
+ export function assertAdapterCertification(report: AdapterCertificationReport): void {
175
+ if (!report.ok) throw new AdapterCertificationError(report)
176
+ }
177
+
178
+ /** Recompute the portable evidence hash. Consumers should verify before trusting a stored report. */
179
+ export async function verifyAdapterCertification(
180
+ report: AdapterCertificationReport,
181
+ ): Promise<boolean> {
182
+ const { evidenceHash, ...body } = report
183
+ return evidenceHash === (await sha256(canonical(body)))
184
+ }
185
+
186
+ // ── Standard profiles ───────────────────────────────────────────────────────────────────────────
187
+
188
+ export interface CertifiableCacheEntry {
189
+ readonly value: unknown
190
+ readonly staleAt: number
191
+ readonly expiresAt: number
192
+ }
193
+
194
+ export interface CertifiableCacheStore {
195
+ get(key: string): CertifiableCacheEntry | undefined | Promise<CertifiableCacheEntry | undefined>
196
+ set(key: string, entry: CertifiableCacheEntry, tags: readonly string[]): void | Promise<void>
197
+ delete(key: string): void | Promise<void>
198
+ invalidateTag(tag: string): void | Promise<void>
199
+ clear(): void | Promise<void>
200
+ }
201
+
202
+ export function cacheStoreCertificationProfile(): AdapterCertificationProfile<CertifiableCacheStore> {
203
+ const entry = (value: unknown): CertifiableCacheEntry => ({
204
+ value,
205
+ staleAt: 5_000_000,
206
+ expiresAt: 6_000_000,
207
+ })
208
+ return {
209
+ id: "cache-store",
210
+ version: 1,
211
+ capabilities: ["roundtrip", "tag-invalidation", "clear"],
212
+ checks: [
213
+ {
214
+ id: "set-get-delete",
215
+ capability: "roundtrip",
216
+ async run(store, context) {
217
+ const key = context.key("roundtrip")
218
+ await store.set(key, entry({ answer: 42 }), [])
219
+ const found = await store.get(key)
220
+ if (JSON.stringify(found?.value) !== '{"answer":42}') throw new Error("CacheRoundtrip")
221
+ await store.delete(key)
222
+ if ((await store.get(key)) !== undefined) throw new Error("CacheDelete")
223
+ },
224
+ },
225
+ {
226
+ id: "invalidate-tag",
227
+ capability: "tag-invalidation",
228
+ async run(store, context) {
229
+ const tagged = context.key("tagged")
230
+ const other = context.key("other")
231
+ const tag = context.key("tag")
232
+ await store.set(tagged, entry(1), [tag])
233
+ await store.set(other, entry(2), [context.key("other-tag")])
234
+ await store.invalidateTag(tag)
235
+ if ((await store.get(tagged)) !== undefined || (await store.get(other))?.value !== 2)
236
+ throw new Error("CacheTagInvalidation")
237
+ },
238
+ },
239
+ {
240
+ id: "clear",
241
+ capability: "clear",
242
+ async run(store, context) {
243
+ const key = context.key("clear")
244
+ await store.set(key, entry(1), [])
245
+ await store.clear()
246
+ if ((await store.get(key)) !== undefined) throw new Error("CacheClear")
247
+ },
248
+ },
249
+ ],
250
+ }
251
+ }
252
+
253
+ export interface CertifiableStoredJob {
254
+ readonly id: string
255
+ readonly name: string
256
+ readonly payload: unknown
257
+ readonly attempt: number
258
+ readonly maxAttempts: number
259
+ }
260
+
261
+ export interface CertifiableJobStore {
262
+ enqueue(job: {
263
+ name: string
264
+ payload: unknown
265
+ runAt: number
266
+ maxAttempts: number
267
+ }): string | Promise<string>
268
+ lease(
269
+ now: number,
270
+ limit: number,
271
+ leaseMs: number,
272
+ ): CertifiableStoredJob[] | Promise<CertifiableStoredJob[]>
273
+ complete(id: string): void | Promise<void>
274
+ retry(id: string, runAt: number): void | Promise<void>
275
+ deadLetter(id: string, error: string): void | Promise<void>
276
+ counts():
277
+ | { pending: number; active: number; dead: number }
278
+ | Promise<{
279
+ pending: number
280
+ active: number
281
+ dead: number
282
+ }>
283
+ }
284
+
285
+ export function jobStoreCertificationProfile(): AdapterCertificationProfile<CertifiableJobStore> {
286
+ return {
287
+ id: "job-store",
288
+ version: 1,
289
+ capabilities: ["lease-complete", "retry-schedule", "dead-letter"],
290
+ checks: [
291
+ {
292
+ id: "lease-complete",
293
+ capability: "lease-complete",
294
+ async run(store) {
295
+ const id = await store.enqueue({
296
+ name: "cert",
297
+ payload: { n: 1 },
298
+ runAt: 10,
299
+ maxAttempts: 3,
300
+ })
301
+ if ((await store.lease(9, 1, 100)).length !== 0) throw new Error("JobEarlyLease")
302
+ const leased = await store.lease(10, 1, 100)
303
+ if (leased.length !== 1 || leased[0]?.id !== id || leased[0]?.attempt !== 0)
304
+ throw new Error("JobLease")
305
+ await store.complete(id)
306
+ if ((await store.counts()).pending !== 0) throw new Error("JobComplete")
307
+ },
308
+ },
309
+ {
310
+ id: "retry-schedule",
311
+ capability: "retry-schedule",
312
+ async run(store) {
313
+ const id = await store.enqueue({ name: "cert", payload: null, runAt: 0, maxAttempts: 3 })
314
+ await store.lease(0, 1, 10)
315
+ await store.retry(id, 20)
316
+ if ((await store.lease(19, 1, 10)).length !== 0) throw new Error("JobRetryEarly")
317
+ const retried = await store.lease(20, 1, 10)
318
+ if (retried[0]?.attempt !== 1) throw new Error("JobRetryAttempt")
319
+ },
320
+ },
321
+ {
322
+ id: "dead-letter",
323
+ capability: "dead-letter",
324
+ async run(store) {
325
+ const id = await store.enqueue({ name: "cert", payload: null, runAt: 0, maxAttempts: 1 })
326
+ await store.deadLetter(id, "bounded-code")
327
+ const counts = await store.counts()
328
+ if (counts.dead !== 1 || counts.pending !== 0) throw new Error("JobDeadLetter")
329
+ },
330
+ },
331
+ ],
332
+ }
333
+ }
334
+
335
+ export interface CertifiableStorageObject {
336
+ readonly body: Uint8Array
337
+ readonly size: number
338
+ readonly contentType?: string
339
+ readonly metadata?: Readonly<Record<string, string>>
340
+ }
341
+
342
+ export interface CertifiableStorageAdapter {
343
+ put(
344
+ key: string,
345
+ data: Uint8Array | ArrayBuffer | string,
346
+ options?: {
347
+ readonly contentType?: string
348
+ readonly metadata?: Readonly<Record<string, string>>
349
+ },
350
+ ): Promise<void>
351
+ get(key: string): Promise<CertifiableStorageObject | null>
352
+ delete(key: string): Promise<void>
353
+ exists(key: string): Promise<boolean>
354
+ list(options?: { readonly prefix?: string; readonly limit?: number }): Promise<string[]>
355
+ listPage?(options?: {
356
+ readonly prefix?: string
357
+ readonly limit?: number
358
+ readonly cursor?: string
359
+ }): Promise<{ readonly keys: readonly string[]; readonly cursor?: string }>
360
+ presign?(
361
+ key: string,
362
+ operation: "get" | "put",
363
+ options?: {
364
+ readonly expiresInSeconds?: number
365
+ readonly contentType?: string
366
+ readonly contentLength?: number
367
+ },
368
+ ): Promise<{ readonly url: string; readonly expiresAt?: Date }>
369
+ copy?(sourceKey: string, destinationKey: string): Promise<void>
370
+ move?(sourceKey: string, destinationKey: string): Promise<void>
371
+ }
372
+
373
+ export function storageAdapterCertificationProfile(
374
+ options: { readonly paging?: boolean; readonly presign?: boolean; readonly move?: boolean } = {},
375
+ ): AdapterCertificationProfile<CertifiableStorageAdapter> {
376
+ const capabilities = ["object-roundtrip", "overwrite", "list-delete"]
377
+ const checks: CertificationCheck<CertifiableStorageAdapter>[] = [
378
+ {
379
+ id: "object-roundtrip",
380
+ capability: "object-roundtrip",
381
+ async run(store, context) {
382
+ const key = context.key("object")
383
+ await store.put(key, "hello", { contentType: "text/plain", metadata: { owner: "cert" } })
384
+ const object = await store.get(key)
385
+ if (
386
+ object === null ||
387
+ new TextDecoder().decode(object.body) !== "hello" ||
388
+ object.size !== 5 ||
389
+ object.contentType !== "text/plain" ||
390
+ object.metadata?.owner !== "cert"
391
+ )
392
+ throw new Error("StorageRoundtrip")
393
+ },
394
+ },
395
+ {
396
+ id: "overwrite",
397
+ capability: "overwrite",
398
+ async run(store, context) {
399
+ const key = context.key("overwrite")
400
+ await store.put(key, "before")
401
+ await store.put(key, "after")
402
+ const object = await store.get(key)
403
+ if (object === null || new TextDecoder().decode(object.body) !== "after")
404
+ throw new Error("StorageOverwrite")
405
+ },
406
+ },
407
+ {
408
+ id: "list-delete",
409
+ capability: "list-delete",
410
+ async run(store, context) {
411
+ const prefix = `${context.key("prefix")}/`
412
+ const key = `${prefix}a`
413
+ await store.put(key, "x")
414
+ await store.put(context.key("outside"), "x")
415
+ const listed = await store.list({ prefix, limit: 1 })
416
+ if (listed.length !== 1 || listed[0] !== key || !(await store.exists(key)))
417
+ throw new Error("StorageList")
418
+ await store.delete(key)
419
+ if ((await store.exists(key)) || (await store.get(key)) !== null)
420
+ throw new Error("StorageDelete")
421
+ },
422
+ },
423
+ ]
424
+ if (options.paging === true) {
425
+ capabilities.push("cursor-paging")
426
+ checks.push({
427
+ id: "cursor-paging",
428
+ capability: "cursor-paging",
429
+ async run(store, context) {
430
+ if (store.listPage === undefined) throw new Error("StoragePagingMissing")
431
+ const prefix = `${context.key("page")}/`
432
+ await store.put(`${prefix}a`, "a")
433
+ await store.put(`${prefix}b`, "b")
434
+ const first = await store.listPage({ prefix, limit: 1 })
435
+ if (first.keys.length !== 1 || first.cursor === undefined)
436
+ throw new Error("StoragePagingFirst")
437
+ const second = await store.listPage({ prefix, limit: 1, cursor: first.cursor })
438
+ if (second.keys.length !== 1 || second.keys[0] === first.keys[0])
439
+ throw new Error("StoragePagingSecond")
440
+ },
441
+ })
442
+ }
443
+ if (options.presign === true) {
444
+ capabilities.push("presign")
445
+ checks.push({
446
+ id: "presign",
447
+ capability: "presign",
448
+ async run(store, context) {
449
+ if (store.presign === undefined) throw new Error("StoragePresignMissing")
450
+ const signed = await store.presign(context.key("signed"), "put", {
451
+ expiresInSeconds: 60,
452
+ contentType: "text/plain",
453
+ contentLength: 1,
454
+ })
455
+ const url = new URL(signed.url)
456
+ if (url.protocol !== "https:" && url.protocol !== "http:")
457
+ throw new Error("StoragePresignUrl")
458
+ },
459
+ })
460
+ }
461
+ if (options.move === true) {
462
+ capabilities.push("copy-move")
463
+ checks.push({
464
+ id: "copy-move",
465
+ capability: "copy-move",
466
+ async run(store, context) {
467
+ if (store.copy === undefined || store.move === undefined)
468
+ throw new Error("StorageMoveMissing")
469
+ const source = context.key("source")
470
+ const copy = context.key("copy")
471
+ const moved = context.key("moved")
472
+ await store.put(source, "x")
473
+ await store.copy(source, copy)
474
+ await store.move(copy, moved)
475
+ if (
476
+ !(await store.exists(source)) ||
477
+ (await store.exists(copy)) ||
478
+ !(await store.exists(moved))
479
+ )
480
+ throw new Error("StorageMove")
481
+ },
482
+ })
483
+ }
484
+ return {
485
+ id: "storage-adapter",
486
+ version: 1,
487
+ capabilities,
488
+ checks,
489
+ }
490
+ }
491
+
492
+ export interface CertifiableRuntimeServer {
493
+ readonly origin: string
494
+ stop(): void | Promise<void>
495
+ }
496
+
497
+ export interface CertifiableRuntimeAdapter {
498
+ start(app: {
499
+ fetch(request: Request): Response | Promise<Response>
500
+ }): CertifiableRuntimeServer | Promise<CertifiableRuntimeServer>
501
+ }
502
+
503
+ export function runtimeAdapterCertificationProfile(): AdapterCertificationProfile<CertifiableRuntimeAdapter> {
504
+ return {
505
+ id: "runtime-adapter",
506
+ version: 1,
507
+ capabilities: ["request-bridge", "response-bridge", "lifecycle"],
508
+ checks: [
509
+ {
510
+ id: "request-bridge",
511
+ capability: "request-bridge",
512
+ async run(adapter) {
513
+ const server = await adapter.start({
514
+ async fetch(request) {
515
+ return Response.json({
516
+ method: request.method,
517
+ path: new URL(request.url).pathname,
518
+ header: request.headers.get("x-cert"),
519
+ body: await request.text(),
520
+ })
521
+ },
522
+ })
523
+ try {
524
+ const response = await fetch(`${server.origin}/cert/path`, {
525
+ method: "POST",
526
+ headers: { "x-cert": "yes" },
527
+ body: "payload",
528
+ })
529
+ const value = (await response.json()) as Record<string, unknown>
530
+ if (
531
+ value.method !== "POST" ||
532
+ value.path !== "/cert/path" ||
533
+ value.header !== "yes" ||
534
+ value.body !== "payload"
535
+ )
536
+ throw new Error("RuntimeRequestBridge")
537
+ } finally {
538
+ await server.stop()
539
+ }
540
+ },
541
+ },
542
+ {
543
+ id: "response-bridge",
544
+ capability: "response-bridge",
545
+ async run(adapter) {
546
+ const server = await adapter.start({
547
+ fetch: () => new Response("certified", { status: 201, headers: { "x-cert": "yes" } }),
548
+ })
549
+ try {
550
+ const response = await fetch(`${server.origin}/`)
551
+ if (
552
+ response.status !== 201 ||
553
+ response.headers.get("x-cert") !== "yes" ||
554
+ (await response.text()) !== "certified"
555
+ )
556
+ throw new Error("RuntimeResponseBridge")
557
+ } finally {
558
+ await server.stop()
559
+ }
560
+ },
561
+ },
562
+ {
563
+ id: "idempotent-stop",
564
+ capability: "lifecycle",
565
+ async run(adapter) {
566
+ const server = await adapter.start({ fetch: () => new Response(null, { status: 204 }) })
567
+ if (!/^https?:\/\//.test(server.origin)) throw new Error("RuntimeOrigin")
568
+ await server.stop()
569
+ await server.stop()
570
+ },
571
+ },
572
+ ],
573
+ }
574
+ }
575
+
576
+ export interface CertifiableDomainEvent {
577
+ readonly id: string
578
+ readonly type: string
579
+ readonly version?: number
580
+ readonly aggregateId: string
581
+ readonly payload: unknown
582
+ readonly timestamp: string
583
+ }
584
+
585
+ export interface CertifiableEventRecord {
586
+ readonly position: string
587
+ readonly event: CertifiableDomainEvent
588
+ readonly claimId: string | null
589
+ }
590
+
591
+ export interface CertifiableEventDeliveryAdapter {
592
+ append(event: CertifiableDomainEvent): Promise<void>
593
+ claimPending(limit: number, now: string): Promise<CertifiableEventRecord[]>
594
+ markDelivered(id: string, claimId: string, at: string): Promise<boolean>
595
+ readPage(options?: { readonly after?: string; readonly limit?: number }): Promise<{
596
+ readonly records: readonly CertifiableEventRecord[]
597
+ readonly nextPosition: string | null
598
+ readonly hasMore: boolean
599
+ }>
600
+ }
601
+
602
+ const certEvent = (id: string, order: number): CertifiableDomainEvent => ({
603
+ id,
604
+ type: "nifra.certified",
605
+ version: 1,
606
+ aggregateId: "certification",
607
+ payload: { order },
608
+ timestamp: new Date(order * 1_000).toISOString(),
609
+ })
610
+
611
+ export function eventDeliveryCertificationProfile(): AdapterCertificationProfile<CertifiableEventDeliveryAdapter> {
612
+ return {
613
+ id: "event-delivery",
614
+ version: 1,
615
+ capabilities: ["idempotent-append", "claim-ownership", "ordered-replay"],
616
+ checks: [
617
+ {
618
+ id: "idempotent-append",
619
+ capability: "idempotent-append",
620
+ async run(store, context) {
621
+ const event = certEvent(context.key("event"), 1)
622
+ await store.append(event)
623
+ await store.append(event)
624
+ const page = await store.readPage({ limit: 10 })
625
+ if (page.records.filter((record) => record.event.id === event.id).length !== 1)
626
+ throw new Error("EventAppendIdempotency")
627
+ },
628
+ },
629
+ {
630
+ id: "claim-ownership",
631
+ capability: "claim-ownership",
632
+ async run(store, context) {
633
+ const event = certEvent(context.key("claim"), 1)
634
+ await store.append(event)
635
+ const claimed = await store.claimPending(1, "2026-01-01T00:00:00.000Z")
636
+ const claimId = claimed[0]?.claimId
637
+ if (claimed[0]?.event.id !== event.id || claimId === null || claimId === undefined)
638
+ throw new Error("EventClaim")
639
+ if (await store.markDelivered(event.id, "wrong-claim", "2026-01-01T00:00:01.000Z"))
640
+ throw new Error("EventClaimFence")
641
+ if (!(await store.markDelivered(event.id, claimId, "2026-01-01T00:00:01.000Z")))
642
+ throw new Error("EventSettlement")
643
+ if ((await store.claimPending(1, "2026-01-01T00:00:02.000Z")).length !== 0)
644
+ throw new Error("EventRedelivery")
645
+ },
646
+ },
647
+ {
648
+ id: "ordered-replay",
649
+ capability: "ordered-replay",
650
+ async run(store, context) {
651
+ const first = certEvent(context.key("first"), 1)
652
+ const second = certEvent(context.key("second"), 2)
653
+ await store.append(first)
654
+ await store.append(second)
655
+ const pageOne = await store.readPage({ limit: 1 })
656
+ if (
657
+ pageOne.records[0]?.event.id !== first.id ||
658
+ pageOne.nextPosition === null ||
659
+ !pageOne.hasMore
660
+ )
661
+ throw new Error("EventReplayPageOne")
662
+ const pageTwo = await store.readPage({ after: pageOne.nextPosition, limit: 1 })
663
+ if (pageTwo.records[0]?.event.id !== second.id || pageTwo.hasMore)
664
+ throw new Error("EventReplayPageTwo")
665
+ },
666
+ },
667
+ ],
668
+ }
669
+ }