@ouraihub/aether-worker 0.1.0 → 0.1.1

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,97 @@
1
+ // In-memory stand-ins for the Durable Object and document storage ports, shared by unit tests and
2
+ // the scenario runner. They hold everything in maps and clone on every read and write, so callers
3
+ // never alias stored values.
4
+ import { KitFailure } from '@ouraihub/aether-core'
5
+ import type { DurableObjectState, DurableObjectStorage } from '../cloudflare.js'
6
+ import type { Actor } from '../context.js'
7
+ import type { DocumentStore, StoredDocument } from '../storage.js'
8
+
9
+ /** Durable Object storage; `writes` records every stored value in order, `alarm` the scheduled alarm. */
10
+ export class MemoryStorage implements DurableObjectStorage {
11
+ readonly records = new Map<string, unknown>()
12
+ readonly writes: unknown[] = []
13
+ alarm: number | null = null
14
+ async get<T = unknown>(key: string): Promise<T | undefined> {
15
+ return structuredClone(this.records.get(key)) as T | undefined
16
+ }
17
+ async put<T>(key: string, value: T): Promise<void> {
18
+ this.records.set(key, structuredClone(value))
19
+ this.writes.push(structuredClone(value))
20
+ }
21
+ async delete(key: string): Promise<boolean> {
22
+ return this.records.delete(key)
23
+ }
24
+ async setAlarm(value: number | Date): Promise<void> {
25
+ this.alarm = Number(value)
26
+ }
27
+ async getAlarm(): Promise<number | null> {
28
+ return this.alarm
29
+ }
30
+ async deleteAlarm(): Promise<void> {
31
+ this.alarm = null
32
+ }
33
+ }
34
+
35
+ /** Durable Object state; `drain()` resolves once every `waitUntil` task (including ones they add) settled. */
36
+ export class MemoryState implements DurableObjectState {
37
+ readonly storage: MemoryStorage
38
+ readonly tasks = new Set<Promise<unknown>>()
39
+ constructor(storage = new MemoryStorage()) {
40
+ this.storage = storage
41
+ }
42
+ blockConcurrencyWhile<T>(callback: () => Promise<T>): Promise<T> {
43
+ return callback()
44
+ }
45
+ waitUntil(promise: Promise<unknown>): void {
46
+ this.tasks.add(promise)
47
+ void promise.finally(() => this.tasks.delete(promise)).catch(() => undefined)
48
+ }
49
+ async drain(): Promise<void> {
50
+ while (this.tasks.size) await Promise.all([...this.tasks])
51
+ }
52
+ }
53
+
54
+ /** Owner-scoped document store with revision compare-and-set; `failConversations` fails conversation writes. */
55
+ export class MemoryDocuments implements DocumentStore {
56
+ readonly documents = new Map<string, StoredDocument>()
57
+ failConversations = false
58
+ private id(owner: Actor, collection: string, key: string): string {
59
+ return JSON.stringify([owner.applicationId, owner.tenantId, owner.subjectId, collection, key])
60
+ }
61
+ async get(owner: Actor, collection: string, key: string): Promise<StoredDocument | null> {
62
+ return structuredClone(this.documents.get(this.id(owner, collection, key)) ?? null)
63
+ }
64
+ async list(owner: Actor, collection: string): Promise<readonly StoredDocument[]> {
65
+ const prefix = JSON.stringify([
66
+ owner.applicationId,
67
+ owner.tenantId,
68
+ owner.subjectId,
69
+ collection,
70
+ ]).slice(0, -1)
71
+ return [...this.documents.entries()]
72
+ .filter(([key]) => key.startsWith(prefix))
73
+ .map(([, value]) => structuredClone(value))
74
+ }
75
+ async compareAndSet(
76
+ owner: Actor,
77
+ collection: string,
78
+ key: string,
79
+ expected: number | null,
80
+ value: unknown,
81
+ ): Promise<StoredDocument> {
82
+ if (collection === 'conversations' && this.failConversations)
83
+ throw new KitFailure('internal', 'secret-token database failure')
84
+ const id = this.id(owner, collection, key),
85
+ old = this.documents.get(id)
86
+ if ((old?.revision ?? null) !== expected) throw new KitFailure('conflict', 'Concurrent update')
87
+ const saved = { key, revision: (old?.revision ?? 0) + 1, value: structuredClone(value) }
88
+ this.documents.set(id, saved)
89
+ return structuredClone(saved)
90
+ }
91
+ async remove(owner: Actor, collection: string, key: string, expected: number): Promise<void> {
92
+ const id = this.id(owner, collection, key)
93
+ if (this.documents.get(id)?.revision !== expected)
94
+ throw new KitFailure('conflict', 'Concurrent update')
95
+ this.documents.delete(id)
96
+ }
97
+ }