refactor(store): extract @uncaged/nerve-store from daemon (#85)
- Create packages/store/ with log-store, log-archive, blob-store (~900 LOC) - daemon depends on @uncaged/nerve-store (workspace:*) - CLI depends on @uncaged/nerve-store, delete daemon-types.ts - Move store-related tests to packages/store/src/__tests__/ - All store tests pass (73/73), no new regressions
This commit is contained in:
@@ -21,13 +21,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@uncaged/nerve-core": "workspace:*",
|
||||
"@uncaged/nerve-store": "workspace:*",
|
||||
"citty": "^0.1.6",
|
||||
"yaml": "^2.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rslib/core": "^0.21.3",
|
||||
"@types/node": "^22.0.0",
|
||||
"@uncaged/nerve-daemon": "workspace:*",
|
||||
"vitest": "^4.1.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,6 @@ export default defineConfig({
|
||||
output: {
|
||||
target: "node",
|
||||
cleanDistPath: true,
|
||||
externals: ["@uncaged/nerve-daemon"],
|
||||
externals: ["@uncaged/nerve-daemon", "@uncaged/nerve-store"],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
/**
|
||||
* Compile-time check: daemon-types.ts stays in sync with @uncaged/nerve-daemon exports.
|
||||
* If the daemon package changes its public API, this file will fail to compile.
|
||||
*/
|
||||
|
||||
import type { SenseInfo } from "@uncaged/nerve-core";
|
||||
import type {
|
||||
ArchiveLogsDayResult as DaemonArchiveLogsDayResult,
|
||||
ArchiveLogsOptions as DaemonArchiveLogsOptions,
|
||||
ArchiveLogsResult as DaemonArchiveLogsResult,
|
||||
LogEntry as DaemonLogEntry,
|
||||
LogQuery as DaemonLogQuery,
|
||||
LogStore as DaemonLogStore,
|
||||
SenseInfo as DaemonSenseInfo,
|
||||
WorkflowRun as DaemonWorkflowRun,
|
||||
WorkflowRunStatus as DaemonWorkflowRunStatus,
|
||||
} from "@uncaged/nerve-daemon";
|
||||
import { describe, expectTypeOf, it } from "vitest";
|
||||
|
||||
import type {
|
||||
ArchiveLogsDayResult,
|
||||
ArchiveLogsOptions,
|
||||
ArchiveLogsResult,
|
||||
LogEntry,
|
||||
LogQuery,
|
||||
LogStore,
|
||||
WorkflowRun,
|
||||
WorkflowRunStatus,
|
||||
} from "../daemon-types.js";
|
||||
|
||||
describe("daemon-types drift guard", () => {
|
||||
it("SenseInfo matches daemon package export (list-senses IPC)", () => {
|
||||
expectTypeOf<SenseInfo>().toMatchTypeOf<DaemonSenseInfo>();
|
||||
expectTypeOf<DaemonSenseInfo>().toMatchTypeOf<SenseInfo>();
|
||||
});
|
||||
|
||||
it("WorkflowRunStatus is assignable both ways", () => {
|
||||
expectTypeOf<WorkflowRunStatus>().toMatchTypeOf<DaemonWorkflowRunStatus>();
|
||||
expectTypeOf<DaemonWorkflowRunStatus>().toMatchTypeOf<WorkflowRunStatus>();
|
||||
});
|
||||
|
||||
it("WorkflowRun is assignable both ways", () => {
|
||||
expectTypeOf<WorkflowRun>().toMatchTypeOf<DaemonWorkflowRun>();
|
||||
expectTypeOf<DaemonWorkflowRun>().toMatchTypeOf<WorkflowRun>();
|
||||
});
|
||||
|
||||
it("LogEntry is assignable both ways", () => {
|
||||
expectTypeOf<LogEntry>().toMatchTypeOf<DaemonLogEntry>();
|
||||
expectTypeOf<DaemonLogEntry>().toMatchTypeOf<LogEntry>();
|
||||
});
|
||||
|
||||
it("LogQuery is assignable both ways", () => {
|
||||
expectTypeOf<LogQuery>().toMatchTypeOf<DaemonLogQuery>();
|
||||
expectTypeOf<DaemonLogQuery>().toMatchTypeOf<LogQuery>();
|
||||
});
|
||||
|
||||
it("LogStore has all required methods", () => {
|
||||
expectTypeOf<LogStore>().toMatchTypeOf<
|
||||
Pick<
|
||||
DaemonLogStore,
|
||||
| "query"
|
||||
| "getWorkflowRun"
|
||||
| "getActiveWorkflowRuns"
|
||||
| "getAllWorkflowRuns"
|
||||
| "upsertWorkflowRun"
|
||||
| "archiveLogs"
|
||||
| "close"
|
||||
>
|
||||
>();
|
||||
});
|
||||
|
||||
it("ArchiveLogs types match daemon", () => {
|
||||
expectTypeOf<ArchiveLogsOptions>().toMatchTypeOf<DaemonArchiveLogsOptions>();
|
||||
expectTypeOf<DaemonArchiveLogsOptions>().toMatchTypeOf<ArchiveLogsOptions>();
|
||||
expectTypeOf<ArchiveLogsResult>().toMatchTypeOf<DaemonArchiveLogsResult>();
|
||||
expectTypeOf<DaemonArchiveLogsResult>().toMatchTypeOf<ArchiveLogsResult>();
|
||||
expectTypeOf<ArchiveLogsDayResult>().toMatchTypeOf<DaemonArchiveLogsDayResult>();
|
||||
expectTypeOf<DaemonArchiveLogsDayResult>().toMatchTypeOf<ArchiveLogsDayResult>();
|
||||
});
|
||||
});
|
||||
@@ -12,7 +12,7 @@ import { createServer } from "node:net";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { createLogStore } from "@uncaged/nerve-daemon";
|
||||
import { createLogStore } from "@uncaged/nerve-store";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
statusIcon,
|
||||
} from "../commands/workflow.js";
|
||||
import { triggerWorkflowViaDaemon } from "../daemon-client.js";
|
||||
import type { LogStore, ThreadRoundRow, WorkflowRun } from "../daemon-types.js";
|
||||
import type { LogStore, ThreadRoundRow, WorkflowRun } from "@uncaged/nerve-store";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test helpers
|
||||
|
||||
@@ -5,7 +5,7 @@ import { defineCommand } from "citty";
|
||||
import { stringify } from "yaml";
|
||||
|
||||
import { triggerWorkflowViaDaemon } from "../daemon-client.js";
|
||||
import type { LogStore, ThreadRoundRow, WorkflowRun } from "../daemon-types.js";
|
||||
import type { LogStore, ThreadRoundRow, WorkflowRun } from "@uncaged/nerve-store";
|
||||
import { loadDaemonModule } from "../workspace-daemon.js";
|
||||
import { getNerveRoot, getSocketPath, isRunning } from "../workspace.js";
|
||||
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
/**
|
||||
* Structural types for workflow CLI — mirrors @uncaged/nerve-daemon log-store
|
||||
* public API so the CLI runtime does not statically depend on the daemon package.
|
||||
*
|
||||
* ⚠️ Keep in sync with @uncaged/nerve-daemon exports.
|
||||
* Run `pnpm --filter @uncaged/nerve-cli test` to catch drift via satisfies assertions.
|
||||
*/
|
||||
|
||||
export type WorkflowRunStatus =
|
||||
| "queued"
|
||||
| "started"
|
||||
| "completed"
|
||||
| "failed"
|
||||
| "crashed"
|
||||
| "dropped"
|
||||
| "interrupted";
|
||||
|
||||
export type WorkflowRun = {
|
||||
runId: string;
|
||||
workflow: string;
|
||||
status: WorkflowRunStatus;
|
||||
ts: number;
|
||||
};
|
||||
|
||||
export type LogEntry = {
|
||||
id?: number;
|
||||
source: string;
|
||||
type: string;
|
||||
refId: string | null;
|
||||
payload: string | null;
|
||||
ts: number;
|
||||
};
|
||||
|
||||
export type LogQuery = {
|
||||
source?: string;
|
||||
type?: string;
|
||||
refId?: string;
|
||||
since?: number;
|
||||
until?: number;
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
export type ArchiveLogsOptions = {
|
||||
now?: number;
|
||||
vacuum?: boolean;
|
||||
maxDays?: number;
|
||||
retentionMs?: number;
|
||||
};
|
||||
|
||||
export type ArchiveLogsDayResult = {
|
||||
day: string;
|
||||
rowCount: number;
|
||||
filePath: string;
|
||||
};
|
||||
|
||||
export type ArchiveLogsResult = {
|
||||
days: ArchiveLogsDayResult[];
|
||||
vacuumed: boolean;
|
||||
};
|
||||
|
||||
/** One role round row — keep in sync with daemon `log-store` `ThreadRoundRow`. */
|
||||
export type ThreadRoundRow = {
|
||||
round: number;
|
||||
logId: number;
|
||||
ts: number;
|
||||
message: { role: string; content: string; meta: unknown; timestamp: number };
|
||||
};
|
||||
|
||||
/** Keep in sync with daemon `log-store` `GetThreadRoundsParams`. */
|
||||
export type GetThreadRoundsParams = {
|
||||
before: number;
|
||||
limit: number;
|
||||
};
|
||||
|
||||
/** Subset of daemon LogStore used by the CLI workflow commands. */
|
||||
export type LogStore = {
|
||||
query: (filter?: LogQuery) => LogEntry[];
|
||||
getWorkflowRun: (runId: string) => WorkflowRun | null;
|
||||
getActiveWorkflowRuns: (workflowName?: string) => WorkflowRun[];
|
||||
getAllWorkflowRuns: (workflowName: string | null) => WorkflowRun[];
|
||||
upsertWorkflowRun: (entry: Omit<LogEntry, "id">, run: WorkflowRun) => LogEntry;
|
||||
getThreadRoundCount: (runId: string) => number;
|
||||
getThreadRounds: (runId: string, params: GetThreadRoundsParams) => ThreadRoundRow[];
|
||||
archiveLogs: (options?: ArchiveLogsOptions) => ArchiveLogsResult;
|
||||
close: () => void;
|
||||
};
|
||||
@@ -5,7 +5,7 @@ import { pathToFileURL } from "node:url";
|
||||
|
||||
import type { NerveConfig } from "@uncaged/nerve-core";
|
||||
|
||||
import type { LogStore } from "./daemon-types.js";
|
||||
import type { LogStore } from "@uncaged/nerve-store";
|
||||
|
||||
export function getDaemonEntryPath(nerveRoot: string): string | undefined {
|
||||
const pkgPath = join(nerveRoot, "node_modules", "@uncaged", "nerve-daemon", "package.json");
|
||||
@@ -29,7 +29,7 @@ export function assertWorkspaceDaemonInstalled(nerveRoot: string): string {
|
||||
return entry;
|
||||
}
|
||||
|
||||
/** Loaded from ~/.uncaged-nerve/node_modules at runtime — keep types structural only. */
|
||||
/** Loaded from ~/.uncaged-nerve/node_modules at runtime. */
|
||||
export type DaemonModule = {
|
||||
createKernel: (
|
||||
config: NerveConfig,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@uncaged/nerve-core": "workspace:*",
|
||||
"@uncaged/nerve-store": "workspace:*",
|
||||
"drizzle-orm": "1.0.0-beta.23-c10d10c",
|
||||
"yaml": "^2.8.3"
|
||||
},
|
||||
|
||||
@@ -47,7 +47,7 @@ vi.mock("node:child_process", () => ({
|
||||
|
||||
// Import after mock is set up
|
||||
const { createKernel } = await import("../kernel.js");
|
||||
const { createLogStore } = await import("../log-store.js");
|
||||
const { createLogStore } = await import("@uncaged/nerve-store");
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
|
||||
@@ -4,8 +4,8 @@ import { join } from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { NerveConfig, Signal } from "@uncaged/nerve-core";
|
||||
import { createLogStore } from "../log-store.js";
|
||||
import type { LogStore } from "../log-store.js";
|
||||
import { createLogStore } from "@uncaged/nerve-store";
|
||||
import type { LogStore } from "@uncaged/nerve-store";
|
||||
import { createReflexScheduler } from "../reflex-scheduler.js";
|
||||
import { createSignalBus } from "../signal-bus.js";
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { drizzle } from "drizzle-orm/node-sqlite";
|
||||
import { integer, real, sqliteTable } from "drizzle-orm/sqlite-core";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { createBlobStore } from "../blob-store.js";
|
||||
import { createBlobStore } from "@uncaged/nerve-store";
|
||||
import { parseParentMessage } from "../ipc.js";
|
||||
import { executeCompute, openPeerDb, openSenseDb, runMigrations } from "../sense-runtime.js";
|
||||
import type { ComputeFn, DrizzleDB, PeerMap, SenseRuntime } from "../sense-runtime.js";
|
||||
|
||||
@@ -35,22 +35,25 @@ export type { SenseInfo } from "./daemon-ipc.js";
|
||||
export { createFileWatcher } from "./file-watcher.js";
|
||||
export type { FileWatcher, FileChange, FileChangeHandler } from "./file-watcher.js";
|
||||
|
||||
export { createBlobStore, normalizeBlobHash } from "./blob-store.js";
|
||||
export type { BlobStore } from "./blob-store.js";
|
||||
|
||||
export { createLogStore, LOG_ARCHIVE_META_KEY } from "./log-store.js";
|
||||
export {
|
||||
createBlobStore,
|
||||
createLogStore,
|
||||
LOG_ARCHIVE_META_KEY,
|
||||
normalizeBlobHash,
|
||||
} from "@uncaged/nerve-store";
|
||||
export type {
|
||||
LogStore,
|
||||
LogEntry,
|
||||
LogQuery,
|
||||
WorkflowRun,
|
||||
WorkflowRunStatus,
|
||||
ArchiveLogsDayResult,
|
||||
ArchiveLogsOptions,
|
||||
ArchiveLogsResult,
|
||||
ThreadRoundRow,
|
||||
BlobStore,
|
||||
GetThreadRoundsParams,
|
||||
} from "./log-store.js";
|
||||
LogEntry,
|
||||
LogQuery,
|
||||
LogStore,
|
||||
ThreadRoundRow,
|
||||
WorkflowRun,
|
||||
WorkflowRunStatus,
|
||||
} from "@uncaged/nerve-store";
|
||||
|
||||
export { createWorkflowManager } from "./workflow-manager.js";
|
||||
export type { WorkflowManager } from "./workflow-manager.js";
|
||||
|
||||
@@ -27,8 +27,8 @@ import { createFileWatcher } from "./file-watcher.js";
|
||||
import type { FileWatcher } from "./file-watcher.js";
|
||||
import type { ComputeMessage, ShutdownMessage } from "./ipc.js";
|
||||
import { parseWorkerMessage } from "./ipc.js";
|
||||
import { createLogStore } from "./log-store.js";
|
||||
import type { LogStore } from "./log-store.js";
|
||||
import { createLogStore } from "@uncaged/nerve-store";
|
||||
import type { LogStore } from "@uncaged/nerve-store";
|
||||
import { createReflexScheduler } from "./reflex-scheduler.js";
|
||||
import type { ReflexScheduler } from "./reflex-scheduler.js";
|
||||
import { createSignalBus } from "./signal-bus.js";
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
*/
|
||||
|
||||
import type { NerveConfig } from "@uncaged/nerve-core";
|
||||
import type { LogStore } from "./log-store.js";
|
||||
import type { LogStore } from "@uncaged/nerve-store";
|
||||
import type { SignalBus, Unsubscribe } from "./signal-bus.js";
|
||||
|
||||
/** Sends a compute message to the worker responsible for the given sense. */
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { NodeSQLiteDatabase } from "drizzle-orm/node-sqlite";
|
||||
import type { Result } from "@uncaged/nerve-core";
|
||||
import { err, ok } from "@uncaged/nerve-core";
|
||||
|
||||
import type { BlobStore } from "./blob-store.js";
|
||||
import type { BlobStore } from "@uncaged/nerve-store";
|
||||
|
||||
/** A Drizzle DB instance (schema-generic) */
|
||||
export type DrizzleDB = NodeSQLiteDatabase<Record<string, never>>;
|
||||
|
||||
@@ -20,7 +20,7 @@ import { join, resolve } from "node:path";
|
||||
import { parseNerveConfig } from "@uncaged/nerve-core";
|
||||
import type { NerveConfig } from "@uncaged/nerve-core";
|
||||
|
||||
import { createBlobStore } from "./blob-store.js";
|
||||
import { createBlobStore } from "@uncaged/nerve-store";
|
||||
import type { WorkerToParentMessage } from "./ipc.js";
|
||||
import { parseParentMessage } from "./ipc.js";
|
||||
import { executeCompute, loadComputeFn, openPeerDb, openSenseDb } from "./sense-runtime.js";
|
||||
|
||||
@@ -21,8 +21,7 @@ import type {
|
||||
ThreadEventMessage,
|
||||
} from "./ipc.js";
|
||||
import { parseWorkerMessage } from "./ipc.js";
|
||||
import type { LogStore } from "./log-store.js";
|
||||
import type { WorkflowRunStatus } from "./log-store.js";
|
||||
import type { LogStore, WorkflowRunStatus } from "@uncaged/nerve-store";
|
||||
import {
|
||||
formatCapturedStderrTail,
|
||||
formatChildExitSummary,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@uncaged/nerve-store",
|
||||
"version": "0.3.0",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": ["dist"],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"prepublishOnly": "bash ../../scripts/prepublish-check.sh",
|
||||
"build": "rslib build",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@uncaged/nerve-core": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rslib/core": "^0.21.3",
|
||||
"@types/node": "^22.0.0",
|
||||
"vitest": "^4.1.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { defineConfig } from "@rslib/core";
|
||||
|
||||
export default defineConfig({
|
||||
lib: [
|
||||
{
|
||||
format: "esm",
|
||||
dts: true,
|
||||
},
|
||||
],
|
||||
source: {
|
||||
entry: {
|
||||
index: "src/index.ts",
|
||||
},
|
||||
},
|
||||
output: {
|
||||
target: "node",
|
||||
cleanDistPath: true,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @uncaged/nerve-store — append-only log storage, cold-archive helpers, CAS blob store.
|
||||
*/
|
||||
|
||||
export * from "./blob-store.js";
|
||||
export * from "./log-archive.js";
|
||||
export { createLogStore } from "./log-store.js";
|
||||
export type {
|
||||
GetThreadRoundsParams,
|
||||
LogEntry,
|
||||
LogQuery,
|
||||
LogStore,
|
||||
ThreadRoundRow,
|
||||
WorkflowRun,
|
||||
WorkflowRunStatus,
|
||||
} from "./log-store.js";
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"composite": false
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Generated
+22
-3
@@ -23,6 +23,9 @@ importers:
|
||||
'@uncaged/nerve-core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
'@uncaged/nerve-store':
|
||||
specifier: workspace:*
|
||||
version: link:../store
|
||||
citty:
|
||||
specifier: ^0.1.6
|
||||
version: 0.1.6
|
||||
@@ -36,9 +39,6 @@ importers:
|
||||
'@types/node':
|
||||
specifier: ^22.0.0
|
||||
version: 22.19.17
|
||||
'@uncaged/nerve-daemon':
|
||||
specifier: workspace:*
|
||||
version: link:../daemon
|
||||
vitest:
|
||||
specifier: ^4.1.5
|
||||
version: 4.1.5(@types/node@22.19.17)(vite@8.0.9(@types/node@22.19.17)(esbuild@0.27.7)(yaml@2.8.3))
|
||||
@@ -61,6 +61,9 @@ importers:
|
||||
'@uncaged/nerve-core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
'@uncaged/nerve-store':
|
||||
specifier: workspace:*
|
||||
version: link:../store
|
||||
drizzle-orm:
|
||||
specifier: 1.0.0-beta.23-c10d10c
|
||||
version: 1.0.0-beta.23-c10d10c(@types/better-sqlite3@7.6.13)(@types/mssql@9.1.11(@azure/core-client@1.10.1))(better-sqlite3@11.10.0)(mssql@11.0.1(@azure/core-client@1.10.1))(sql.js@1.14.1)
|
||||
@@ -78,6 +81,22 @@ importers:
|
||||
specifier: ^4.1.5
|
||||
version: 4.1.5(@types/node@22.19.17)(vite@8.0.9(@types/node@22.19.17)(esbuild@0.27.7)(yaml@2.8.3))
|
||||
|
||||
packages/store:
|
||||
dependencies:
|
||||
'@uncaged/nerve-core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
devDependencies:
|
||||
'@rslib/core':
|
||||
specifier: ^0.21.3
|
||||
version: 0.21.3(typescript@5.9.3)
|
||||
'@types/node':
|
||||
specifier: ^22.0.0
|
||||
version: 22.19.17
|
||||
vitest:
|
||||
specifier: ^4.1.5
|
||||
version: 4.1.5(@types/node@22.19.17)(vite@8.0.9(@types/node@22.19.17)(esbuild@0.27.7)(yaml@2.8.3))
|
||||
|
||||
packages:
|
||||
|
||||
'@ast-grep/napi-darwin-arm64@0.37.0':
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
},
|
||||
"references": [
|
||||
{ "path": "packages/core" },
|
||||
{ "path": "packages/store" },
|
||||
{ "path": "packages/cli" },
|
||||
{ "path": "packages/daemon" }
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user