mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Extract reverse-lookup scan to core/relationships (one shared impl)
This commit is contained in:
@@ -224,38 +224,56 @@ export function recomputeDatasetRefs(spec: Json): string[] {
|
||||
## 4. Reverse lookup: who uses this dataset?
|
||||
|
||||
The Dataset Manager shows a **usage badge** and a **Linked Snippets** list; the
|
||||
Snippet Library shows a snippet's linked datasets. Both come from one selector
|
||||
scan — no stored back-pointer to drift.
|
||||
Snippet Library shows a snippet's linked datasets. Both come from one scan — no
|
||||
stored back-pointer to drift.
|
||||
|
||||
The scan itself is **pure** and lives in core, taking the snippets as a parameter
|
||||
so the _same_ implementation serves two callers: the reactive UI (which passes its
|
||||
live `snippets` selection straight in) and the non-reactive service wrapper (which
|
||||
passes a store snapshot for programmatic callers). This is what makes "who
|
||||
references this" have exactly one implementation (§6).
|
||||
|
||||
```ts
|
||||
// src/app/services/RelationshipService.ts
|
||||
|
||||
import { useSnippetStore } from '../stores/SnippetStore';
|
||||
import type { Snippet } from '../../core/types';
|
||||
// src/core/relationships.ts — pure scan (unit-tested)
|
||||
|
||||
/** Snippets whose datasetRefs include `name` (case-insensitive). */
|
||||
export function findSnippetsReferencingDataset(name: string): Snippet[] {
|
||||
export function snippetsReferencingDataset<T extends { datasetRefs: readonly string[] }>(
|
||||
snippets: readonly T[],
|
||||
name: string,
|
||||
): T[] {
|
||||
const lower = name.toLowerCase();
|
||||
return useSnippetStore
|
||||
.getState()
|
||||
.snippets.filter((s) => s.datasetRefs.some((ref) => ref.toLowerCase() === lower));
|
||||
return snippets.filter((s) => s.datasetRefs.some((ref) => ref.toLowerCase() === lower));
|
||||
}
|
||||
|
||||
/** Count for the usage badge. */
|
||||
export function datasetUsageCount(name: string): number {
|
||||
return findSnippetsReferencingDataset(name).length;
|
||||
/** Usage counts for the whole library in one pass, keyed by lower-cased name. */
|
||||
export function datasetUsageCounts(
|
||||
snippets: readonly { datasetRefs: readonly string[] }[],
|
||||
): Map<string, number>;
|
||||
```
|
||||
|
||||
```ts
|
||||
// src/app/services/RelationshipService.ts — snapshot wrapper for non-reactive callers
|
||||
|
||||
import { snippetsReferencingDataset } from '../../core/relationships';
|
||||
import { useSnippetStore } from '../stores/SnippetStore';
|
||||
|
||||
export function findSnippetsReferencingDataset(name: string): Snippet[] {
|
||||
return snippetsReferencingDataset(useSnippetStore.getState().snippets, name);
|
||||
}
|
||||
```
|
||||
|
||||
Because this reads `useSnippetStore.getState().snippets`, exposing it as a
|
||||
selector for the UI makes the badge and Linked Snippets list reactive for free —
|
||||
they update the moment any snippet is published with changed refs.
|
||||
The reactive UI does **not** go through the service: a component subscribed to
|
||||
`snippets` calls the core helper directly, so the badge and Linked Snippets list
|
||||
update the moment any snippet is published with changed refs — no duplicated
|
||||
matching logic, no `getState()` snapshot that would miss updates.
|
||||
|
||||
**Do**
|
||||
|
||||
- Keep reverse lookup a pure scan over the store. It is O(snippets) but the
|
||||
collections are small (library budget ~5 MB); clarity beats an index.
|
||||
- Expose it as a selector where the UI needs reactivity.
|
||||
- Keep reverse lookup a pure scan, parameterized on the snippets so both the
|
||||
reactive and snapshot callers share it. It is O(snippets) but the collections
|
||||
are small (library budget ~5 MB); clarity beats an index.
|
||||
- Call the core helper directly from a store-subscribed component for reactivity;
|
||||
use the service wrapper only from non-reactive (snapshot) code.
|
||||
|
||||
**Don't**
|
||||
|
||||
@@ -464,14 +482,15 @@ export function renameDatasetEverywhere(oldName: string, newName: string): { upd
|
||||
|
||||
## 7. Where things live
|
||||
|
||||
| Concern | Location | Pure? | Tested |
|
||||
| --------------------------------------------- | ----------------------------------------- | ------------------------------ | ---------------- |
|
||||
| `makeUniqueName`, `isNameTaken` | `src/core/naming.ts` | yes | unit |
|
||||
| `extractDatasetRefs`, `recomputeDatasetRefs` | `src/core/spec-refs.ts` | yes | unit |
|
||||
| `renameDatasetInSpec` | `src/core/spec-refs.ts` | yes | unit |
|
||||
| `findSnippetsReferencingDataset`, usage count | `src/app/services/RelationshipService.ts` | no (reads store) | integration |
|
||||
| `renameDatasetEverywhere` | `src/app/services/RelationshipService.ts` | no (mutates stores) | integration |
|
||||
| `dedupeIncomingDatasetNames` | `src/app/services/ImportService.ts` | nearly (uses `makeUniqueName`) | unit/integration |
|
||||
| Concern | Location | Pure? | Tested |
|
||||
| ------------------------------------------------------------------------ | ----------------------------------------- | ------------------------------ | ---------------- |
|
||||
| `makeUniqueName`, `isNameTaken` | `src/core/naming.ts` | yes | unit |
|
||||
| `extractDatasetRefs`, `recomputeDatasetRefs` | `src/core/spec-refs.ts` | yes | unit |
|
||||
| `renameDatasetInSpec` | `src/core/spec-refs.ts` | yes | unit |
|
||||
| `snippetsReferencingDataset`, `datasetUsageCounts` (reverse-lookup scan) | `src/core/relationships.ts` | yes | unit |
|
||||
| `findSnippetsReferencingDataset`, usage count (snapshot wrappers) | `src/app/services/RelationshipService.ts` | no (reads store) | integration |
|
||||
| `renameDatasetEverywhere` → `{ updated }` | `src/app/services/RelationshipService.ts` | no (mutates stores) | integration |
|
||||
| `dedupeIncomingDatasetNames` | `src/app/services/ImportService.ts` | nearly (uses `makeUniqueName`) | unit/integration |
|
||||
|
||||
The dividing line: anything that takes plain data and returns plain data is
|
||||
**core** and unit-tested in isolation; anything that reaches into a Zustand store
|
||||
|
||||
Reference in New Issue
Block a user