mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-05 11:22:11 +02:00
Use HashMap for config service state
This commit is contained in:
parent
48710a0518
commit
475bc3cb6c
2 changed files with 140 additions and 116 deletions
|
|
@ -1896,6 +1896,30 @@ Notes:
|
||||||
- `cd ts && bun run lint`
|
- `cd ts && bun run lint`
|
||||||
- `git diff --check`
|
- `git diff --check`
|
||||||
|
|
||||||
|
### 2026-06-04: Config Service HashMap State Slice
|
||||||
|
|
||||||
|
- Status: migrated and package-verified.
|
||||||
|
- Completed:
|
||||||
|
- `ts/packages/flow/src/config/service.ts` now stores workspace and
|
||||||
|
namespace config state in nested `HashMap` values inside the existing
|
||||||
|
`SynchronizedRef`.
|
||||||
|
- Put/delete operations now update immutable `HashMap` snapshots with
|
||||||
|
`HashMap.set` and `HashMap.remove` instead of cloning and mutating native
|
||||||
|
`Map` instances.
|
||||||
|
- Persistence, config dump, list, and get-values handlers keep plain
|
||||||
|
`Record`/array shapes only at API and JSON boundaries, with deterministic
|
||||||
|
ordering applied while converting out of `HashMap`.
|
||||||
|
- The focused scan for native map state in `config/service.ts` is clean; the
|
||||||
|
remaining map matches in that file are `HashMap` and `SynchronizedRef`
|
||||||
|
operations.
|
||||||
|
- Verification:
|
||||||
|
- `cd ts/packages/flow && bunx --bun vitest run src/__tests__/config-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
|
## Subagent Findings To Preserve
|
||||||
|
|
||||||
- MCP/workbench:
|
- MCP/workbench:
|
||||||
|
|
@ -1912,10 +1936,14 @@ Notes:
|
||||||
matches are legacy migration fallbacks, QA assertions, or the pre-paint
|
matches are legacy migration fallbacks, QA assertions, or the pre-paint
|
||||||
host script.
|
host script.
|
||||||
- Flow stateful services:
|
- Flow stateful services:
|
||||||
- Config service, KnowledgeCore service, FlowManager, and Librarian
|
- Config service operation dispatch, schema persistence, and nested
|
||||||
ref-backed state slices are complete. Follow-up service work should focus
|
workspace state are complete: the long-lived config store now uses
|
||||||
on scoped layers, schedules where polling semantics allow, and managed
|
`HashMap` inside `SynchronizedRef`, and plain records remain only at
|
||||||
persistence providers rather than direct mutable service fields.
|
persistence/API boundaries.
|
||||||
|
- KnowledgeCore service, FlowManager, and Librarian ref-backed state slices
|
||||||
|
are complete. Follow-up service work should focus on scoped layers,
|
||||||
|
schedules where polling semantics allow, and managed persistence providers
|
||||||
|
rather than direct mutable service fields.
|
||||||
- Flow service startup facades now consistently use `ManagedRuntime`, and
|
- Flow service startup facades now consistently use `ManagedRuntime`, and
|
||||||
local scripts should delegate to `runMain()` instead of adding local
|
local scripts should delegate to `runMain()` instead of adding local
|
||||||
`.catch(console.error/process.exit)` wrappers.
|
`.catch(console.error/process.exit)` wrappers.
|
||||||
|
|
@ -2023,14 +2051,18 @@ Notes:
|
||||||
- ConfigService and KnowledgeCore operation dispatch now use `effect/Match`
|
- ConfigService and KnowledgeCore operation dispatch now use `effect/Match`
|
||||||
with `Match.exhaustive`; FlowManager and Librarian operation dispatch now
|
with `Match.exhaustive`; FlowManager and Librarian operation dispatch now
|
||||||
use `effect/Match` with runtime-preserving `Match.orElse` fallbacks.
|
use `effect/Match` with runtime-preserving `Match.orElse` fallbacks.
|
||||||
|
- ConfigService nested workspace config state now uses Effect `HashMap`.
|
||||||
|
JSON and API response helpers intentionally convert back to sorted plain
|
||||||
|
records/arrays at the boundary.
|
||||||
- Native `switch` statements are now clean in `ts/packages`; future branch
|
- Native `switch` statements are now clean in `ts/packages`; future branch
|
||||||
drift should keep service dispatch on `effect/Match` or Schema tagged-union
|
drift should keep service dispatch on `effect/Match` or Schema tagged-union
|
||||||
helpers.
|
helpers.
|
||||||
- Client RPC/BaseApi connection-state fanout now uses
|
- Client RPC/BaseApi connection-state fanout now uses
|
||||||
`effect/SubscriptionRef`; remaining gateway/client P1 work is broader API
|
`effect/SubscriptionRef`; remaining gateway/client P1 work is broader API
|
||||||
design, not listener bookkeeping.
|
design, not listener bookkeeping.
|
||||||
- Long-lived `Map` / `Set` state in ref-backed services can move toward
|
- Long-lived `Map` / `Set` state in remaining ref-backed services can move
|
||||||
Effect collections later; local pure traversal maps/sets remain no-ops.
|
toward Effect collections later; local pure traversal maps/sets remain
|
||||||
|
no-ops.
|
||||||
|
|
||||||
## Ranked Findings
|
## Ranked Findings
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {NodeRuntime} from "@effect/platform-node";
|
import {NodeRuntime} from "@effect/platform-node";
|
||||||
import {Duration, Effect, Layer, ManagedRuntime, Match, SynchronizedRef} from "effect";
|
import {Duration, Effect, HashMap, Layer, ManagedRuntime, Match, Option, SynchronizedRef} from "effect";
|
||||||
import * as Predicate from "effect/Predicate";
|
import * as Predicate from "effect/Predicate";
|
||||||
import * as S from "effect/Schema";
|
import * as S from "effect/Schema";
|
||||||
import {
|
import {
|
||||||
|
|
@ -70,13 +70,14 @@ interface ConfigValueLike {
|
||||||
readonly value: unknown;
|
readonly value: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
type NamespaceStore = Map<string, unknown>;
|
type NamespaceStore = HashMap.HashMap<string, unknown>;
|
||||||
type WorkspaceStore = Map<string, NamespaceStore>;
|
type WorkspaceStore = HashMap.HashMap<string, NamespaceStore>;
|
||||||
|
type ConfigStore = HashMap.HashMap<string, WorkspaceStore>;
|
||||||
type WorkspaceSnapshot = Record<string, Record<string, Record<string, unknown>>>;
|
type WorkspaceSnapshot = Record<string, Record<string, Record<string, unknown>>>;
|
||||||
|
|
||||||
interface ConfigServiceState {
|
interface ConfigServiceState {
|
||||||
readonly version: number;
|
readonly version: number;
|
||||||
readonly store: Map<string, WorkspaceStore>;
|
readonly store: ConfigStore;
|
||||||
readonly consumer: BackendConsumer<ConfigRequest> | null;
|
readonly consumer: BackendConsumer<ConfigRequest> | null;
|
||||||
readonly responseProducer: BackendProducer<ConfigResponse> | null;
|
readonly responseProducer: BackendProducer<ConfigResponse> | null;
|
||||||
readonly pushProducer: BackendProducer<ConfigPush> | null;
|
readonly pushProducer: BackendProducer<ConfigPush> | null;
|
||||||
|
|
@ -116,46 +117,46 @@ export interface ConfigService extends AsyncProcessorRuntime<ConfigServiceError>
|
||||||
|
|
||||||
const initialState = (): ConfigServiceState => ({
|
const initialState = (): ConfigServiceState => ({
|
||||||
version: 0,
|
version: 0,
|
||||||
store: new Map<string, WorkspaceStore>(),
|
store: HashMap.empty<string, WorkspaceStore>(),
|
||||||
consumer: null,
|
consumer: null,
|
||||||
responseProducer: null,
|
responseProducer: null,
|
||||||
pushProducer: null,
|
pushProducer: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const cloneNamespaceStore = (source: NamespaceStore): NamespaceStore => {
|
const getHashMapValue = <K, V>(store: HashMap.HashMap<K, V>, key: K): V | undefined =>
|
||||||
const next = new Map<string, unknown>();
|
Option.getOrUndefined(HashMap.get(store, key));
|
||||||
for (const [key, value] of source) {
|
|
||||||
next.set(key, value);
|
|
||||||
}
|
|
||||||
return next;
|
|
||||||
};
|
|
||||||
|
|
||||||
const cloneWorkspaceStore = (source: WorkspaceStore): WorkspaceStore => {
|
const compareText = (left: string, right: string): number =>
|
||||||
const next = new Map<string, NamespaceStore>();
|
left.localeCompare(right);
|
||||||
for (const [namespace, subMap] of source) {
|
|
||||||
next.set(namespace, cloneNamespaceStore(subMap));
|
|
||||||
}
|
|
||||||
return next;
|
|
||||||
};
|
|
||||||
|
|
||||||
const cloneConfigStore = (source: Map<string, WorkspaceStore>): Map<string, WorkspaceStore> => {
|
const compareWorkspace = (left: string, right: string): number =>
|
||||||
const next = new Map<string, WorkspaceStore>();
|
left === right
|
||||||
for (const [workspace, ws] of source) {
|
? 0
|
||||||
next.set(workspace, cloneWorkspaceStore(ws));
|
: left === DEFAULT_WORKSPACE
|
||||||
}
|
? -1
|
||||||
return next;
|
: right === DEFAULT_WORKSPACE
|
||||||
};
|
? 1
|
||||||
|
: compareText(left, right);
|
||||||
|
|
||||||
|
const workspaceEntries = (store: ConfigStore): ReadonlyArray<readonly [string, WorkspaceStore]> =>
|
||||||
|
HashMap.toEntries(store).sort(([left], [right]) => compareWorkspace(left, right));
|
||||||
|
|
||||||
|
const namespaceEntries = (store: WorkspaceStore): ReadonlyArray<readonly [string, NamespaceStore]> =>
|
||||||
|
HashMap.toEntries(store).sort(([left], [right]) => compareText(left, right));
|
||||||
|
|
||||||
|
const valueEntries = (store: NamespaceStore): ReadonlyArray<readonly [string, unknown]> =>
|
||||||
|
HashMap.toEntries(store).sort(([left], [right]) => compareText(left, right));
|
||||||
|
|
||||||
const toPersistedWorkspaces = (
|
const toPersistedWorkspaces = (
|
||||||
store: Map<string, WorkspaceStore>,
|
store: ConfigStore,
|
||||||
): WorkspaceSnapshot => {
|
): WorkspaceSnapshot => {
|
||||||
const workspaces: WorkspaceSnapshot = {};
|
const workspaces: WorkspaceSnapshot = {};
|
||||||
|
|
||||||
for (const [workspace, ws] of store) {
|
for (const [workspace, ws] of workspaceEntries(store)) {
|
||||||
const workspaceData: Record<string, Record<string, unknown>> = {};
|
const workspaceData: Record<string, Record<string, unknown>> = {};
|
||||||
for (const [namespace, subMap] of ws) {
|
for (const [namespace, subMap] of namespaceEntries(ws)) {
|
||||||
const obj: Record<string, unknown> = {};
|
const obj: Record<string, unknown> = {};
|
||||||
for (const [key, value] of subMap) {
|
for (const [key, value] of valueEntries(subMap)) {
|
||||||
obj[key] = value;
|
obj[key] = value;
|
||||||
}
|
}
|
||||||
workspaceData[namespace] = obj;
|
workspaceData[namespace] = obj;
|
||||||
|
|
@ -166,34 +167,33 @@ const toPersistedWorkspaces = (
|
||||||
return workspaces;
|
return workspaces;
|
||||||
};
|
};
|
||||||
|
|
||||||
const storeFromPersistedConfig = (parsed: PersistedConfig): Map<string, WorkspaceStore> => {
|
const workspaceStoreFromPersistedNamespaces = (
|
||||||
const store = new Map<string, WorkspaceStore>();
|
namespaces: Record<string, Record<string, unknown>>,
|
||||||
|
): WorkspaceStore => {
|
||||||
|
let workspaceStore = HashMap.empty<string, NamespaceStore>();
|
||||||
|
|
||||||
|
for (const [namespace, obj] of Object.entries(namespaces)) {
|
||||||
|
let namespaceStore = HashMap.empty<string, unknown>();
|
||||||
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
|
namespaceStore = HashMap.set(namespaceStore, key, value);
|
||||||
|
}
|
||||||
|
workspaceStore = HashMap.set(workspaceStore, namespace, namespaceStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
return workspaceStore;
|
||||||
|
};
|
||||||
|
|
||||||
|
const storeFromPersistedConfig = (parsed: PersistedConfig): ConfigStore => {
|
||||||
|
let store = HashMap.empty<string, WorkspaceStore>();
|
||||||
|
|
||||||
if (parsed.workspaces !== undefined) {
|
if (parsed.workspaces !== undefined) {
|
||||||
for (const [workspace, namespaces] of Object.entries(parsed.workspaces)) {
|
for (const [workspace, namespaces] of Object.entries(parsed.workspaces)) {
|
||||||
const ws = new Map<string, NamespaceStore>();
|
store = HashMap.set(store, workspace, workspaceStoreFromPersistedNamespaces(namespaces));
|
||||||
for (const [namespace, obj] of Object.entries(namespaces)) {
|
|
||||||
const subMap = new Map<string, unknown>();
|
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
|
||||||
subMap.set(key, value);
|
|
||||||
}
|
|
||||||
ws.set(namespace, subMap);
|
|
||||||
}
|
|
||||||
store.set(workspace, ws);
|
|
||||||
}
|
}
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ws = new Map<string, NamespaceStore>();
|
return HashMap.set(store, DEFAULT_WORKSPACE, workspaceStoreFromPersistedNamespaces(parsed.data ?? {}));
|
||||||
for (const [namespace, obj] of Object.entries(parsed.data ?? {})) {
|
|
||||||
const subMap = new Map<string, unknown>();
|
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
|
||||||
subMap.set(key, value);
|
|
||||||
}
|
|
||||||
ws.set(namespace, subMap);
|
|
||||||
}
|
|
||||||
store.set(DEFAULT_WORKSPACE, ws);
|
|
||||||
return store;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const optionalString = (value: unknown): string | undefined =>
|
const optionalString = (value: unknown): string | undefined =>
|
||||||
|
|
@ -266,38 +266,14 @@ const getWorkspaceStore = (
|
||||||
state: ConfigServiceState,
|
state: ConfigServiceState,
|
||||||
workspace: string,
|
workspace: string,
|
||||||
): WorkspaceStore | undefined =>
|
): WorkspaceStore | undefined =>
|
||||||
state.store.get(workspace);
|
getHashMapValue(state.store, workspace);
|
||||||
|
|
||||||
const getNamespaceStore = (
|
const getNamespaceStore = (
|
||||||
state: ConfigServiceState,
|
state: ConfigServiceState,
|
||||||
workspace: string,
|
workspace: string,
|
||||||
namespace: string,
|
namespace: string,
|
||||||
): NamespaceStore | undefined =>
|
): NamespaceStore | undefined =>
|
||||||
getWorkspaceStore(state, workspace)?.get(namespace);
|
Option.flatMap(HashMap.get(state.store, workspace), HashMap.get(namespace)).pipe(Option.getOrUndefined);
|
||||||
|
|
||||||
const getOrCreateWorkspaceStore = (
|
|
||||||
store: Map<string, WorkspaceStore>,
|
|
||||||
workspace: string,
|
|
||||||
): WorkspaceStore => {
|
|
||||||
const existing = store.get(workspace);
|
|
||||||
if (existing !== undefined) return existing;
|
|
||||||
const created = new Map<string, NamespaceStore>();
|
|
||||||
store.set(workspace, created);
|
|
||||||
return created;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getOrCreateNamespaceStore = (
|
|
||||||
store: Map<string, WorkspaceStore>,
|
|
||||||
workspace: string,
|
|
||||||
namespace: string,
|
|
||||||
): NamespaceStore => {
|
|
||||||
const ws = getOrCreateWorkspaceStore(store, workspace);
|
|
||||||
const existing = ws.get(namespace);
|
|
||||||
if (existing !== undefined) return existing;
|
|
||||||
const created = new Map<string, unknown>();
|
|
||||||
ws.set(namespace, created);
|
|
||||||
return created;
|
|
||||||
};
|
|
||||||
|
|
||||||
const configDumpForState = (
|
const configDumpForState = (
|
||||||
state: ConfigServiceState,
|
state: ConfigServiceState,
|
||||||
|
|
@ -308,9 +284,9 @@ const configDumpForState = (
|
||||||
|
|
||||||
if (ws === undefined) return config;
|
if (ws === undefined) return config;
|
||||||
|
|
||||||
for (const [namespace, subMap] of ws) {
|
for (const [namespace, subMap] of namespaceEntries(ws)) {
|
||||||
const obj: Record<string, unknown> = {};
|
const obj: Record<string, unknown> = {};
|
||||||
for (const [key, value] of subMap) {
|
for (const [key, value] of valueEntries(subMap)) {
|
||||||
obj[key] = value;
|
obj[key] = value;
|
||||||
}
|
}
|
||||||
config[namespace] = obj;
|
config[namespace] = obj;
|
||||||
|
|
@ -412,7 +388,7 @@ const handleGetWithState = (
|
||||||
type: key.type,
|
type: key.type,
|
||||||
key: key.key ?? "",
|
key: key.key ?? "",
|
||||||
value: key.key !== undefined
|
value: key.key !== undefined
|
||||||
? getNamespaceStore(state, workspace, key.type)?.get(key.key)
|
? getHashMapValue(getNamespaceStore(state, workspace, key.type) ?? HashMap.empty<string, unknown>(), key.key)
|
||||||
: undefined,
|
: undefined,
|
||||||
}));
|
}));
|
||||||
return {version: state.version, values};
|
return {version: state.version, values};
|
||||||
|
|
@ -429,13 +405,14 @@ const handleGetWithState = (
|
||||||
|
|
||||||
if (subMap !== undefined) {
|
if (subMap !== undefined) {
|
||||||
if (keys.length === 1) {
|
if (keys.length === 1) {
|
||||||
for (const [key, value] of subMap) {
|
for (const [key, value] of valueEntries(subMap)) {
|
||||||
values[key] = value;
|
values[key] = value;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (const key of keys.slice(1)) {
|
for (const key of keys.slice(1)) {
|
||||||
if (subMap.has(key)) {
|
const value = getHashMapValue(subMap, key);
|
||||||
values[key] = subMap.get(key);
|
if (value !== undefined || HashMap.has(subMap, key)) {
|
||||||
|
values[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -448,11 +425,15 @@ const applyPut = (
|
||||||
state: ConfigServiceState,
|
state: ConfigServiceState,
|
||||||
values: ReadonlyArray<ConfigValueLike>,
|
values: ReadonlyArray<ConfigValueLike>,
|
||||||
): ConfigServiceState => {
|
): ConfigServiceState => {
|
||||||
const store = cloneConfigStore(state.store);
|
let store = state.store;
|
||||||
|
|
||||||
for (const item of values) {
|
for (const item of values) {
|
||||||
getOrCreateNamespaceStore(store, item.workspace ?? DEFAULT_WORKSPACE, item.type)
|
const workspace = item.workspace ?? DEFAULT_WORKSPACE;
|
||||||
.set(item.key, item.value);
|
const workspaceStore = getHashMapValue(store, workspace) ?? HashMap.empty<string, NamespaceStore>();
|
||||||
|
const namespaceStore = getHashMapValue(workspaceStore, item.type) ?? HashMap.empty<string, unknown>();
|
||||||
|
const nextNamespaceStore = HashMap.set(namespaceStore, item.key, item.value);
|
||||||
|
const nextWorkspaceStore = HashMap.set(workspaceStore, item.type, nextNamespaceStore);
|
||||||
|
store = HashMap.set(store, workspace, nextWorkspaceStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -467,21 +448,27 @@ const applyDeleteObjectKeys = (
|
||||||
workspace: string,
|
workspace: string,
|
||||||
keys: ReadonlyArray<ConfigKeyLike>,
|
keys: ReadonlyArray<ConfigKeyLike>,
|
||||||
): ConfigServiceState => {
|
): ConfigServiceState => {
|
||||||
const store = cloneConfigStore(state.store);
|
let store = state.store;
|
||||||
const ws = store.get(workspace);
|
const ws = getHashMapValue(store, workspace);
|
||||||
|
|
||||||
if (ws !== undefined) {
|
if (ws !== undefined) {
|
||||||
|
let nextWorkspaceStore = ws;
|
||||||
|
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
if (key.key === undefined) {
|
if (key.key === undefined) {
|
||||||
ws.delete(key.type);
|
nextWorkspaceStore = HashMap.remove(nextWorkspaceStore, key.type);
|
||||||
} else {
|
} else {
|
||||||
const ns = ws.get(key.type);
|
const ns = getHashMapValue(nextWorkspaceStore, key.type);
|
||||||
ns?.delete(key.key);
|
if (ns !== undefined) {
|
||||||
if (ns !== undefined && ns.size === 0) {
|
const nextNamespaceStore = HashMap.remove(ns, key.key);
|
||||||
ws.delete(key.type);
|
nextWorkspaceStore = HashMap.size(nextNamespaceStore) === 0
|
||||||
|
? HashMap.remove(nextWorkspaceStore, key.type)
|
||||||
|
: HashMap.set(nextWorkspaceStore, key.type, nextNamespaceStore);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
store = HashMap.set(store, workspace, nextWorkspaceStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -496,26 +483,31 @@ const applyDeleteStringKeys = (
|
||||||
workspace: string,
|
workspace: string,
|
||||||
keys: ReadonlyArray<string>,
|
keys: ReadonlyArray<string>,
|
||||||
): ConfigServiceState => {
|
): ConfigServiceState => {
|
||||||
const store = cloneConfigStore(state.store);
|
let store = state.store;
|
||||||
const namespace = keys[0];
|
const namespace = keys[0];
|
||||||
const ws = store.get(workspace);
|
const ws = getHashMapValue(store, workspace);
|
||||||
|
|
||||||
if (ws === undefined) return state;
|
if (ws === undefined) return state;
|
||||||
|
|
||||||
|
let nextWorkspaceStore = ws;
|
||||||
|
|
||||||
if (keys.length === 1) {
|
if (keys.length === 1) {
|
||||||
ws.delete(namespace);
|
nextWorkspaceStore = HashMap.remove(nextWorkspaceStore, namespace);
|
||||||
} else {
|
} else {
|
||||||
const subMap = ws.get(namespace);
|
const subMap = getHashMapValue(nextWorkspaceStore, namespace);
|
||||||
if (subMap !== undefined) {
|
if (subMap !== undefined) {
|
||||||
|
let nextNamespaceStore = subMap;
|
||||||
for (const key of keys.slice(1)) {
|
for (const key of keys.slice(1)) {
|
||||||
subMap.delete(key);
|
nextNamespaceStore = HashMap.remove(nextNamespaceStore, key);
|
||||||
}
|
|
||||||
if (subMap.size === 0) {
|
|
||||||
ws.delete(namespace);
|
|
||||||
}
|
}
|
||||||
|
nextWorkspaceStore = HashMap.size(nextNamespaceStore) === 0
|
||||||
|
? HashMap.remove(nextWorkspaceStore, namespace)
|
||||||
|
: HashMap.set(nextWorkspaceStore, namespace, nextNamespaceStore);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
store = HashMap.set(store, workspace, nextWorkspaceStore);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
store,
|
store,
|
||||||
|
|
@ -588,14 +580,14 @@ const handleListWithState = (
|
||||||
if (namespace === undefined) {
|
if (namespace === undefined) {
|
||||||
return {
|
return {
|
||||||
version: state.version,
|
version: state.version,
|
||||||
directory: ws !== undefined ? [...ws.keys()] : [],
|
directory: ws !== undefined ? namespaceEntries(ws).map(([key]) => key) : [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const subMap = ws?.get(namespace);
|
const subMap = ws === undefined ? undefined : getHashMapValue(ws, namespace);
|
||||||
return {
|
return {
|
||||||
version: state.version,
|
version: state.version,
|
||||||
directory: subMap !== undefined ? [...subMap.keys()] : [],
|
directory: subMap !== undefined ? valueEntries(subMap).map(([key]) => key) : [],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -609,9 +601,9 @@ const handleGetValuesWithState = (
|
||||||
const values: Array<{type: string; key: string; value: unknown}> = [];
|
const values: Array<{type: string; key: string; value: unknown}> = [];
|
||||||
|
|
||||||
if (ws !== undefined) {
|
if (ws !== undefined) {
|
||||||
for (const [namespace, subMap] of ws) {
|
for (const [namespace, subMap] of namespaceEntries(ws)) {
|
||||||
if (type.length > 0 && namespace !== type) continue;
|
if (type.length > 0 && namespace !== type) continue;
|
||||||
for (const [key, value] of subMap) {
|
for (const [key, value] of valueEntries(subMap)) {
|
||||||
values.push({type: namespace, key, value});
|
values.push({type: namespace, key, value});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -627,10 +619,10 @@ const handleGetValuesAllWorkspacesWithState = (
|
||||||
const type = requestType(request) ?? "";
|
const type = requestType(request) ?? "";
|
||||||
const values: Array<{workspace: string; type: string; key: string; value: unknown}> = [];
|
const values: Array<{workspace: string; type: string; key: string; value: unknown}> = [];
|
||||||
|
|
||||||
for (const [workspace, ws] of state.store) {
|
for (const [workspace, ws] of workspaceEntries(state.store)) {
|
||||||
for (const [namespace, subMap] of ws) {
|
for (const [namespace, subMap] of namespaceEntries(ws)) {
|
||||||
if (type.length > 0 && namespace !== type) continue;
|
if (type.length > 0 && namespace !== type) continue;
|
||||||
for (const [key, value] of subMap) {
|
for (const [key, value] of valueEntries(subMap)) {
|
||||||
values.push({workspace, type: namespace, key, value});
|
values.push({workspace, type: namespace, key, value});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -840,7 +832,7 @@ export function makeConfigService(config: ConfigServiceConfig): ConfigService {
|
||||||
store: storeFromPersistedConfig(parsed),
|
store: storeFromPersistedConfig(parsed),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
yield* Effect.log(`[ConfigService] Loaded persisted config (version=${next.version}, workspaces=${next.store.size})`);
|
yield* Effect.log(`[ConfigService] Loaded persisted config (version=${next.version}, workspaces=${HashMap.size(next.store)})`);
|
||||||
});
|
});
|
||||||
|
|
||||||
service = Object.assign(base, {
|
service = Object.assign(base, {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue