@cp949/registry
v1.0.0
Published
Map-based typed key-value registry with lookup, iteration and grouping helpers. Zero dependencies.
Maintainers
Readme
@cp949/registry
rxjs 없이 사용하는 가벼운 key-value 저장소.
내부적으로 Map을 사용하며, bare Map에 없는 조회·순회·그룹화 헬퍼와 의존성 주입을 위한 읽기 전용 계약(ReadableRegistry)을 제공한다.
설치
pnpm add @cp949/registry
# npm install @cp949/registryMap 대비 추가된 기능
| 기능 | bare Map | Registry |
|------|-----------|------------|
| 삽입 순서 보장 | O | O |
| 단건 조회 | get | get |
| 술어 검색 | X | findValue, filterValues, existsValue |
| 인덱스 검색 | X | findKeyIndex, findValueIndex, findLastValueIndex |
| 그룹화 | X | groupBy |
| 일괄 교체 | X | replaceAll |
| 정적 팩토리 | X | fromValues |
| 읽기 전용 계약 | X | ReadableRegistry 인터페이스 |
| 얕은 복사 | X | clone() |
반응형 저장소가 필요하다면
@cp949/rx-registry를 사용한다.
key 타입
내부 저장소가 Map이므로 key 타입에 별도 제약이 없다.
string과 number가 가장 흔하지만, object identity나 symbol을 key로 쓸 수도 있다.
// object key — 참조 동일성(===) 기준
const key = { id: 1 };
const r = new Registry<typeof key, string>();
r.add(key, "value");
r.get(key); // "value"
// symbol key
const sym = Symbol("item");
const r2 = new Registry<symbol, number>();
r2.add(sym, 42);
r2.get(sym); // 42예시 코드에서 string key를 사용하는 것은 가장 흔한 패턴이어서이지, 타입 제한이 아니다.
기본 사용
import { Registry } from "@cp949/registry";
const r = new Registry<string, { id: string; name: string }>();
r.add("a", { id: "a", name: "Alice" });
r.add("b", { id: "b", name: "Bob" });
r.size(); // 2
r.get("a"); // { id: "a", name: "Alice" }
r.exists("b"); // true
r.remove("b");
r.size(); // 1
r.clear();
r.size(); // 0일괄 로드
const users = [
{ id: "a", name: "Alice", role: "admin" },
{ id: "b", name: "Bob", role: "user" },
];
// 배열에서 생성 — keyFn 필수, valueFn 생략하면 항목 자체가 값이 된다
const r = Registry.fromValues(users, { keyFn: (u) => u.id });
// 이미 있는 레지스트리에 일괄 교체
r.replaceAll(users, (u) => u.id);
// 여러 항목 추가 (기존 데이터 유지)
r.addAll(users, (u) => u.id);
// 없을 때만 추가
r.addIfNotExists("c", { id: "c", name: "Carol", role: "user" });순회
r.keys(); // ["a", "b"]
r.values(); // [{ id: "a", ... }, { id: "b", ... }]
r.entries(); // [["a", { id: "a", ... }], ["b", { id: "b", ... }]]
// mapper를 넘기면 변환된 배열을 반환한다
r.values((u) => u.name); // ["Alice", "Bob"]
// 부수효과가 필요할 때
r.forEachValues((u) => console.log(u.name));검색
// 술어를 만족하는 첫 번째 값
r.findValue((u) => u.role === "admin"); // { id: "a", name: "Alice", ... }
// 술어를 만족하는 모든 값
r.filterValues((u) => u.role === "user"); // [{ id: "b", ... }]
// 조건 충족 여부만 확인 (Array.some 상당)
r.existsValue((u) => u.name === "Alice"); // true
// 키 인덱스 (삽입 순서 기준)
r.findKeyIndex((k) => k === "b"); // 1
// 값 인덱스
r.findValueIndex((u) => u.role === "admin"); // 0
r.findLastValueIndex((u) => u.role === "user"); // 1그룹화
// Map<TGroupKey, TValue[]> 반환
const byRole = r.groupBy((u) => u.role);
byRole.get("admin"); // [{ id: "a", ... }]
byRole.get("user"); // [{ id: "b", ... }]
// valueFn으로 변환하면서 그룹화
const namesByRole = r.groupBy(
(u) => u.role,
(u) => u.name,
);
namesByRole.get("user"); // ["Bob"]얕은 복사
const copy = r.clone();
copy.add("c", { id: "c", name: "Carol", role: "user" });
r.size(); // 2 — 원본 영향 없음
copy.size(); // 3ReadableRegistry 인터페이스
의존성 주입이나 테스트 시 쓰기 능력을 숨기고 읽기 전용 뷰만 노출할 때 사용한다.
import type { ReadableRegistry } from "@cp949/registry";
function summarize(
reg: ReadableRegistry<string, { name: string }>,
) {
return reg.values((u) => u.name).join(", ");
}
summarize(r); // "Alice, Bob"Registry는 ReadableRegistry를 구현하므로 별도 변환 없이 전달할 수 있다.
주의 사항
Registry는 반응형 저장소가 아니다
변경 구독(subscribe), 시그널, 이벤트 방출이 없다. Observable 통합이 필요하면 @cp949/rx-registry를 사용한다.
v0 → v1 변경 사항
| 항목 | v0 (Object 저장) | v1 (Map 저장) |
|------|-----------------|--------------|
| 숫자 키 | 문자열로 강제 변환됨 | 숫자 그대로 보존 |
| remove() 반환값 | void | this (메서드 체이닝 가능) |
v1.0 → 현재
| 항목 | 변경 내용 |
|------|----------|
| TKey 제약 | extends string \| number 제거 — Map이 허용하는 모든 타입 사용 가능 |
| TValue 제약 | extends string \| number \| boolean \| object 제거 |
