Use MutableHashMap for collection manager

This commit is contained in:
elpresidank 2026-06-04 07:41:19 -05:00
parent 069d901737
commit 7d77a5c1de
3 changed files with 85 additions and 12 deletions

View file

@ -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

View file

@ -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([]);
});
});

View file

@ -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<string, CollectionEntry>();
const collections = MutableHashMap.empty<string, CollectionEntry>();
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);
}
},
};