From 7d77a5c1de47ca11284bdd17368fe8cc2d78efaf Mon Sep 17 00:00:00 2001 From: elpresidank Date: Thu, 4 Jun 2026 07:41:19 -0500 Subject: [PATCH] Use MutableHashMap for collection manager --- ts/EFFECT_NATIVE_REWRITE_AUDIT.md | 26 +++++++++-- .../src/__tests__/collection-manager.test.ts | 44 +++++++++++++++++++ .../flow/src/librarian/collection-manager.ts | 27 ++++++++---- 3 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 ts/packages/flow/src/__tests__/collection-manager.test.ts diff --git a/ts/EFFECT_NATIVE_REWRITE_AUDIT.md b/ts/EFFECT_NATIVE_REWRITE_AUDIT.md index 272a3e6b..19429f61 100644 --- a/ts/EFFECT_NATIVE_REWRITE_AUDIT.md +++ b/ts/EFFECT_NATIVE_REWRITE_AUDIT.md @@ -2234,6 +2234,26 @@ Notes: - `cd ts && bun run lint` - `git diff --check` +### 2026-06-04: Librarian Collection Manager MutableHashMap Slice + +- Status: migrated and package-verified. +- Completed: + - `ts/packages/flow/src/librarian/collection-manager.ts` now stores + in-memory collection entries in `MutableHashMap` instead of native `Map`. + - Public `listCollections`, `getCollection`, `updateCollection`, + `deleteCollection`, `ensureCollectionExists`, `toJSON`, and `loadFromJSON` + behavior stays array/boolean/undefined based at the API and persistence + boundaries. + - New focused coverage verifies create/update/list/get/delete and JSON + restore behavior through the migrated collection state. +- Verification: + - `cd ts/packages/flow && bunx --bun vitest run src/__tests__/collection-manager.test.ts src/__tests__/librarian-service.test.ts` + - `cd ts && bun run check:tsgo` + - `cd ts && bun run build` + - `cd ts && bun run test` + - `cd ts && bun run lint` + - `git diff --check` + ## Subagent Findings To Preserve - MCP/workbench: @@ -2420,9 +2440,9 @@ Notes: The workbench random id helper is complete; the remaining workbench `Effect.gen` match is a local one-shot command effect value. - Remaining real long-lived native collection targets include base processor - registries, Librarian service / collection manager state, prompt template - cache, and a workbench module cache. Local traversal sets and test fakes - remain no-op boundaries. + registries, Librarian service state, prompt template cache, and a workbench + module cache. The standalone Librarian collection manager is complete. + Local traversal sets and test fakes remain no-op boundaries. ## Ranked Findings diff --git a/ts/packages/flow/src/__tests__/collection-manager.test.ts b/ts/packages/flow/src/__tests__/collection-manager.test.ts new file mode 100644 index 00000000..9a5420ba --- /dev/null +++ b/ts/packages/flow/src/__tests__/collection-manager.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from "vitest"; +import { makeCollectionManager } from "../librarian/collection-manager.js"; + +describe("CollectionManager", () => { + it("manages collection entries through Effect MutableHashMap state", () => { + const manager = makeCollectionManager(); + + const created = manager.ensureCollectionExists("alice", "papers"); + expect(created).toEqual({ + user: "alice", + collection: "papers", + name: "papers", + description: "", + tags: [], + }); + + const updated = manager.updateCollection( + "alice", + "papers", + "Research Papers", + "Curated PDFs", + ["research", "pdf"], + ); + manager.updateCollection("bob", "notes", "Notes", "", []); + + expect(manager.getCollection("alice", "papers")).toEqual(updated); + expect(manager.listCollections("alice")).toEqual([updated]); + expect(manager.toJSON()).toEqual([updated, { + user: "bob", + collection: "notes", + name: "Notes", + description: "", + tags: [], + }]); + + expect(manager.deleteCollection("alice", "papers")).toBe(true); + expect(manager.deleteCollection("alice", "papers")).toBe(false); + expect(manager.getCollection("alice", "papers")).toBeUndefined(); + + manager.loadFromJSON([updated]); + expect(manager.listCollections("alice")).toEqual([updated]); + expect(manager.listCollections("bob")).toEqual([]); + }); +}); diff --git a/ts/packages/flow/src/librarian/collection-manager.ts b/ts/packages/flow/src/librarian/collection-manager.ts index 46dfb537..a00d82c5 100644 --- a/ts/packages/flow/src/librarian/collection-manager.ts +++ b/ts/packages/flow/src/librarian/collection-manager.ts @@ -6,6 +6,9 @@ * via the parent LibrarianService JSON snapshot). */ +import * as MutableHashMap from "effect/MutableHashMap"; +import * as O from "effect/Option"; + export interface CollectionEntry { user: string; collection: string; @@ -32,7 +35,7 @@ export interface CollectionManager { export function makeCollectionManager(): CollectionManager { /** keyed by `${user}:${collection}` */ - const collections = new Map(); + const collections = MutableHashMap.empty(); const key = (user: string, collection: string): string => `${user}:${collection}`; @@ -44,35 +47,41 @@ export function makeCollectionManager(): CollectionManager { tags: string[], ): CollectionEntry => { const entry: CollectionEntry = { user, collection, name, description, tags }; - collections.set(key(user, collection), entry); + MutableHashMap.set(collections, key(user, collection), entry); return entry; }; return { listCollections: (user) => { const result: CollectionEntry[] = []; - for (const entry of collections.values()) { + for (const entry of MutableHashMap.values(collections)) { if (entry.user === user) { result.push(entry); } } return result; }, - getCollection: (user, collection) => collections.get(key(user, collection)), + getCollection: (user, collection) => + O.getOrUndefined(MutableHashMap.get(collections, key(user, collection))), updateCollection, - deleteCollection: (user, collection) => collections.delete(key(user, collection)), + deleteCollection: (user, collection) => { + const collectionKey = key(user, collection); + const exists = MutableHashMap.has(collections, collectionKey); + MutableHashMap.remove(collections, collectionKey); + return exists; + }, ensureCollectionExists: (user, collection) => { - const existing = collections.get(key(user, collection)); + const existing = O.getOrUndefined(MutableHashMap.get(collections, key(user, collection))); if (existing !== undefined) return existing; return updateCollection(user, collection, collection, "", []); }, /** Serialize to a plain array for JSON persistence. */ - toJSON: () => [...collections.values()], + toJSON: () => Array.from(MutableHashMap.values(collections)), /** Restore from a serialized array. */ loadFromJSON: (entries) => { - collections.clear(); + MutableHashMap.clear(collections); for (const entry of entries) { - collections.set(key(entry.user, entry.collection), entry); + MutableHashMap.set(collections, key(entry.user, entry.collection), entry); } }, };