(storage: PluginStorageFacade)
| 36 | }; |
| 37 | |
| 38 | export const makeShapeMemory = (storage: PluginStorageFacade): ShapeMemory => { |
| 39 | const cache = new Map<string, ObservedShape | null>(); |
| 40 | const persisted = new Map<string, string>(); |
| 41 | |
| 42 | const cacheKey = (owner: Owner, address: string) => `${owner}:${address}`; |
| 43 | |
| 44 | const load = (address: string, owner: Owner): Effect.Effect<ObservedShape | null> => |
| 45 | Effect.gen(function* () { |
| 46 | const key = cacheKey(owner, address); |
| 47 | const hit = cache.get(key); |
| 48 | if (hit !== undefined) return hit; |
| 49 | const entry = yield* storage |
| 50 | .getForOwner<ObservedShape>({ owner, collection: COLLECTION, key: address }) |
| 51 | .pipe(Effect.catch(() => Effect.succeed(null))); |
| 52 | const record = entry?.data ?? null; |
| 53 | cache.set(key, record); |
| 54 | if (record !== null) persisted.set(key, JSON.stringify(record.schema)); |
| 55 | return record; |
| 56 | }); |
| 57 | |
| 58 | const observe = (address: string, owner: Owner, value: unknown): Effect.Effect<void> => |
| 59 | Effect.gen(function* () { |
| 60 | const key = cacheKey(owner, address); |
| 61 | const prior = yield* load(address, owner); |
| 62 | const now = yield* Clock.currentTimeMillis; |
| 63 | const next = observeShape(prior, value, now); |
| 64 | cache.set(key, next); |
| 65 | // Write only when the merged shape actually changed — observation |
| 66 | // counters alone are bookkeeping, not worth a row write per call. |
| 67 | const schemaJson = JSON.stringify(next.schema); |
| 68 | if (persisted.get(key) === schemaJson) return; |
| 69 | yield* storage |
| 70 | .put({ owner, collection: COLLECTION, key: address, data: next }) |
| 71 | .pipe(Effect.catch(() => Effect.succeed(null))); |
| 72 | persisted.set(key, schemaJson); |
| 73 | }).pipe(Effect.catchCause(() => Effect.void)); |
| 74 | |
| 75 | return { |
| 76 | observe, |
| 77 | recall: (address, owner) => |
| 78 | load(address, owner).pipe(Effect.catchCause(() => Effect.succeed(null))), |
| 79 | }; |
| 80 | }; |
| 81 | |
| 82 | /** |
| 83 | * Render a remembered shape as the JSON Schema served in place of a missing |
no test coverage detected