refactor(state): inline canonical database schemas at build

This commit is contained in:
Peter Steinberger
2026-08-01 06:53:57 -07:00
parent 56ffa2bca4
commit b2ec661afe
26 changed files with 141 additions and 2885 deletions

View File

@@ -183,7 +183,7 @@ without exceptions outside doctor/import/export/debug boundaries.
- [x] Keep Kysely generated types aligned after any schema change.
Files: `src/state/openclaw-state-schema.sql`,
`src/state/openclaw-agent-schema.sql`,
`src/state/*generated*`.
`src/state/*-db.generated.d.ts`.
Proof: no schema change in this pass; `pnpm db:kysely:check`;
`pnpm lint:kysely`.
- [x] Re-run focused tests for touched stores, commands, and scripts.
@@ -296,12 +296,12 @@ The branch already has a real shared SQLite base:
macOS runtime locator, CI, and public install docs all agree.
- `src/state/openclaw-state-db.ts` opens `openclaw.sqlite`, sets WAL,
`synchronous=NORMAL`, `busy_timeout=30000`, `foreign_keys=ON`, and applies
the generated schema module derived from
the build-inlined schema bytes derived from
`src/state/openclaw-state-schema.sql`.
- Kysely table types and runtime schema modules are generated from disposable
SQLite databases created from the committed `.sql` files; runtime code no
longer keeps copy-pasted schema strings for global, per-agent, or proxy
capture databases.
- Kysely table types are generated from disposable SQLite databases created
from the committed `.sql` files. Source runs read those canonical files;
packaged builds inline the same bytes, so runtime code keeps no committed
copy-pasted schema strings or packaged SQL assets.
- Runtime stores derive selected and inserted row types from those generated
Kysely `DB` interfaces instead of shadowing SQLite row shapes by hand. Raw SQL
remains limited to schema application, pragmas, and migration-only DDL.

View File

@@ -31,6 +31,7 @@ const TSDOWN_SOURCE_EXTENSIONS = [
".json5",
".mjs",
".mts",
".sql",
".ts",
".tsx",
".yaml",

View File

@@ -10,15 +10,11 @@ const SCHEMAS = [
name: "openclaw-state",
schema: "src/state/openclaw-state-schema.sql",
outFile: "src/state/openclaw-state-db.generated.d.ts",
schemaOutFile: "src/state/openclaw-state-schema.generated.ts",
schemaExport: "OPENCLAW_STATE_SCHEMA_SQL",
},
{
name: "openclaw-agent",
schema: "src/state/openclaw-agent-schema.sql",
outFile: "src/state/openclaw-agent-db.generated.d.ts",
schemaOutFile: "src/state/openclaw-agent-schema.generated.ts",
schemaExport: "OPENCLAW_AGENT_SCHEMA_SQL",
},
];
@@ -118,39 +114,19 @@ function readUtf8(file) {
return fs.readFileSync(file, "utf8");
}
function generatedSchemaModule(schema) {
const source = readUtf8(schema.schema).trimEnd();
const literal = source.replaceAll("\\", "\\\\").replaceAll("`", "\\`").replaceAll("${", "\\${");
return [
"/**",
" * This file was generated from the SQLite schema source.",
" * Please do not edit it manually.",
" */",
"",
`export const ${schema.schemaExport} = \`${literal}\\n\`;`,
"",
].join("\n");
}
function generate(schema) {
const db = new DatabaseSync(":memory:");
try {
db.exec(readUtf8(schema.schema));
const typesSource = generateTypes(db);
const schemaSource = generatedSchemaModule(schema);
if (verify) {
if (typesSource !== readUtf8(schema.outFile)) {
console.error(`${schema.outFile} is out of date. Run pnpm db:kysely:gen.`);
process.exitCode = 1;
}
if (schemaSource !== readUtf8(schema.schemaOutFile)) {
console.error(`${schema.schemaOutFile} is out of date. Run pnpm db:kysely:gen.`);
process.exitCode = 1;
}
} else {
fs.writeFileSync(schema.outFile, typesSource);
fs.writeFileSync(schema.schemaOutFile, schemaSource);
}
} finally {
db.close();

View File

@@ -7,10 +7,10 @@ import type { RuntimeEnv } from "../runtime.js";
import { createLocalSqliteSnapshotProvider } from "../snapshot/local-repository.js";
import { OPENCLAW_AGENT_SCHEMA_VERSION } from "../state/openclaw-agent-db.js";
import { resolveOpenClawAgentSqlitePath } from "../state/openclaw-agent-db.paths.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../state/openclaw-agent-schema.generated.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../state/openclaw-agent-schema.js";
import { OPENCLAW_STATE_SCHEMA_VERSION } from "../state/openclaw-state-db.js";
import { resolveOpenClawStateSqlitePath } from "../state/openclaw-state-db.paths.js";
import { OPENCLAW_STATE_SCHEMA_SQL } from "../state/openclaw-state-schema.generated.js";
import { OPENCLAW_STATE_SCHEMA_SQL } from "../state/openclaw-state-schema.js";
import {
backupSqliteCreateCommand,
backupSqliteListCommand,

View File

@@ -12,7 +12,7 @@ import {
clearOpenClawAgentDatabaseOpenFailure,
migrateOpenClawAgentDatabaseForMaintenance,
} from "../state/openclaw-agent-db.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../state/openclaw-agent-schema.generated.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../state/openclaw-agent-schema.js";
import { OPENCLAW_SQLITE_BUSY_TIMEOUT_MS } from "../state/openclaw-state-db.js";
import {
createSessionSqliteMigrationFailureIssue,

View File

@@ -14,7 +14,7 @@ import {
OPENCLAW_STATE_SCHEMA_VERSION,
} from "../state/openclaw-state-db.js";
import { resolveOpenClawStateSqlitePath } from "../state/openclaw-state-db.paths.js";
import { OPENCLAW_STATE_SCHEMA_SQL } from "../state/openclaw-state-schema.generated.js";
import { OPENCLAW_STATE_SCHEMA_SQL } from "../state/openclaw-state-schema.js";
import { runDoctorStateSqliteCompact } from "./doctor-state-sqlite-compact.js";
const tempDirs = useAutoCleanupTempDirTracker((cleanup) => {

View File

@@ -34,7 +34,7 @@ import {
OPENCLAW_AGENT_SCHEMA_VERSION,
type OpenClawAgentDatabase,
} from "../state/openclaw-agent-db.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../state/openclaw-agent-schema.generated.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../state/openclaw-agent-schema.js";
import { OPENCLAW_SQLITE_BUSY_TIMEOUT_MS } from "../state/openclaw-state-db.js";
import { VERSION } from "../version.js";
import { repairGatewayAgentMediaMigrationStartupFailures } from "./gateway-boot-lifecycle.js";

View File

@@ -1,8 +1,14 @@
// Covers bundling rules encoded in the root tsdown config.
import { readFileSync } from "node:fs";
import path from "node:path";
import { bundledPluginRoot } from "openclaw/plugin-sdk/test-fixtures";
import { describe, expect, it } from "vitest";
import tsdownConfig from "../../tsdown.config.ts";
import tsdownConfig, {
createStateSchemaInlinePlugin,
STATE_SCHEMA_INLINE_PLUGIN_NAME,
} from "../../tsdown.config.ts";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../state/openclaw-agent-schema.js";
import { OPENCLAW_STATE_SCHEMA_SQL } from "../state/openclaw-state-schema.js";
type TsdownConfigEntry = {
deps?: {
@@ -12,6 +18,7 @@ type TsdownConfigEntry = {
entry?: Record<string, string> | string[];
inputOptions?: TsdownInputOptions;
outDir?: string;
plugins?: Array<{ name?: string }>;
};
type TsdownLog = {
@@ -87,6 +94,53 @@ function readAgentAuthDiscoverySource(): string {
}
describe("tsdown config", () => {
it.each([
{
exportName: "OPENCLAW_STATE_SCHEMA_SQL",
modulePath: "src/state/openclaw-state-schema.ts",
schemaPath: "src/state/openclaw-state-schema.sql",
sourceValue: OPENCLAW_STATE_SCHEMA_SQL,
},
{
exportName: "OPENCLAW_AGENT_SCHEMA_SQL",
modulePath: "src/state/openclaw-agent-schema.ts",
schemaPath: "src/state/openclaw-agent-schema.sql",
sourceValue: OPENCLAW_AGENT_SCHEMA_SQL,
},
])("inlines canonical schema bytes for $modulePath", (schema) => {
const rootDir = process.cwd();
const watchedPaths: string[] = [];
const plugin = createStateSchemaInlinePlugin(rootDir);
const result = plugin.load.call(
{ addWatchFile: (filePath: string) => watchedPaths.push(filePath) },
path.resolve(rootDir, schema.modulePath),
);
const schemaPath = path.resolve(rootDir, schema.schemaPath);
const canonicalSql = readFileSync(schemaPath, "utf8");
expect(result).not.toBeNull();
const match = result?.code.match(
new RegExp(`^export const ${schema.exportName} = (.*);\\n$`, "su"),
);
expect(match?.[1]).toBeDefined();
expect(JSON.parse(match?.[1] ?? "null")).toBe(canonicalSql);
expect(schema.sourceValue).toBe(canonicalSql);
expect(watchedPaths).toEqual([schemaPath]);
});
it("installs schema inlining only on the unified runtime graph", () => {
const unifiedGraph = requireUnifiedDistGraph();
const inlinePlugins = asConfigArray(tsdownConfig).flatMap(
(config) =>
config.plugins?.filter((plugin) => plugin.name === STATE_SCHEMA_INLINE_PLUGIN_NAME) ?? [],
);
expect(unifiedGraph.plugins).toContainEqual(
expect.objectContaining({ name: STATE_SCHEMA_INLINE_PLUGIN_NAME }),
);
expect(inlinePlugins).toHaveLength(1);
});
it("keeps core, plugin runtime, plugin-sdk, bundled root plugins, and bundled hooks in one dist graph", () => {
const distGraph = requireUnifiedDistGraph();

View File

@@ -8,9 +8,9 @@ import { requireNodeSqlite } from "../infra/node-sqlite.js";
import { createPrivateSqliteDirectory } from "../infra/sqlite-private-directory.js";
import { runExec } from "../process/exec.js";
import { OPENCLAW_AGENT_SCHEMA_VERSION } from "../state/openclaw-agent-db.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../state/openclaw-agent-schema.generated.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../state/openclaw-agent-schema.js";
import { OPENCLAW_STATE_SCHEMA_VERSION } from "../state/openclaw-state-db.js";
import { OPENCLAW_STATE_SCHEMA_SQL } from "../state/openclaw-state-schema.generated.js";
import { OPENCLAW_STATE_SCHEMA_SQL } from "../state/openclaw-state-schema.js";
import { hashSnapshotArtifact, readSnapshotManifest } from "./manifest.js";
import {
SNAPSHOT_MANIFEST_FILENAME,

View File

@@ -1,5 +1,5 @@
import type { DatabaseSync } from "node:sqlite";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.generated.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.js";
const BOARD_SCHEMA_START = "CREATE TABLE IF NOT EXISTS board_tabs (";
const BOARD_SCHEMA_END = "CREATE TABLE IF NOT EXISTS heartbeat_outcomes (";

View File

@@ -20,7 +20,7 @@ import {
readExistingAgentSchemaMeta,
} from "./openclaw-agent-db-schema-helpers.js";
import { ensureOpenClawAgentDatabaseSchema } from "./openclaw-agent-db-schema.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.generated.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.js";
import { OPENCLAW_SQLITE_BUSY_TIMEOUT_MS } from "./openclaw-state-db.js";
/** Require exact agent ownership without requiring the latest schema. */

View File

@@ -23,7 +23,7 @@ import {
} from "./openclaw-agent-board-schema.js";
import { OPENCLAW_AGENT_SCHEMA_VERSION } from "./openclaw-agent-db-contract.js";
import { OpenClawAgentDatabaseMediaMigrationRequiredError } from "./openclaw-agent-db-migration-required.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.generated.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.js";
import {
AGENT_V14_ADDITIVE_SCHEMA_SQL,
AGENT_V14_CORE_SCHEMA_SQL,

View File

@@ -48,7 +48,7 @@ import {
} from "./openclaw-agent-db-session-provenance.js";
import type { DB as OpenClawAgentKyselyDatabase } from "./openclaw-agent-db.generated.js";
import { resolveOpenClawAgentSqlitePath } from "./openclaw-agent-db.paths.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.generated.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.js";
import { OPENCLAW_SQLITE_BUSY_TIMEOUT_MS } from "./openclaw-state-db.js";
type OpenClawAgentMetadataDatabase = Pick<OpenClawAgentKyselyDatabase, "schema_meta">;

View File

@@ -1,639 +0,0 @@
/**
* This file was generated from the SQLite schema source.
* Please do not edit it manually.
*/
export const OPENCLAW_AGENT_SCHEMA_SQL = `-- Session storage doctrine: session_nodes.entry_json is the canonical logical-session
-- record. Promoted session_nodes columns are query indexes projected only by the
-- session entry writer; session_windows and their children own transcript generations.
CREATE TABLE IF NOT EXISTS schema_meta (
meta_key TEXT NOT NULL PRIMARY KEY,
role TEXT NOT NULL,
schema_version INTEGER NOT NULL,
agent_id TEXT,
app_version TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
) STRICT;
CREATE TABLE IF NOT EXISTS state_leases (
scope TEXT NOT NULL,
lease_key TEXT NOT NULL,
owner TEXT NOT NULL,
expires_at INTEGER,
heartbeat_at INTEGER,
payload_json TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
PRIMARY KEY (scope, lease_key)
) STRICT;
CREATE INDEX IF NOT EXISTS idx_agent_state_leases_expiry
ON state_leases(expires_at, scope, lease_key)
WHERE expires_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_agent_state_leases_owner
ON state_leases(owner, updated_at DESC);
CREATE TABLE IF NOT EXISTS session_nodes (
session_key TEXT NOT NULL PRIMARY KEY,
current_session_id TEXT NOT NULL,
entry_json TEXT NOT NULL,
entry_valid INTEGER NOT NULL DEFAULT 0 CHECK (entry_valid IN (-1, 0, 1)),
updated_at INTEGER NOT NULL,
status TEXT CHECK (status IS NULL OR status IN ('running', 'done', 'failed', 'killed', 'timeout')),
created_at INTEGER,
created_via TEXT CHECK (created_via IS NULL OR created_via IN ('operator', 'spawn', 'channel', 'cron', 'talk', 'run', 'plugin', 'internal')),
created_actor_type TEXT CHECK (created_actor_type IS NULL OR created_actor_type IN ('human', 'agent', 'system')),
created_actor_id TEXT,
parent_session_key TEXT,
spawned_by TEXT,
fork_source_session_key TEXT,
fork_source_session_id TEXT,
fork_source_entry_id TEXT,
label TEXT,
display_name TEXT,
category TEXT,
icon TEXT,
pinned_at INTEGER,
archived_at INTEGER,
last_read_at INTEGER,
last_interaction_at INTEGER,
last_activity_at INTEGER
) STRICT;
CREATE INDEX IF NOT EXISTS idx_agent_session_nodes_updated_at
ON session_nodes(updated_at DESC, session_key);
CREATE INDEX IF NOT EXISTS idx_agent_session_nodes_last_interaction_at
ON session_nodes(last_interaction_at DESC, session_key);
CREATE INDEX IF NOT EXISTS idx_agent_session_nodes_parent_session_key
ON session_nodes(parent_session_key, session_key);
CREATE INDEX IF NOT EXISTS idx_agent_session_nodes_spawned_by
ON session_nodes(spawned_by, session_key);
CREATE INDEX IF NOT EXISTS idx_agent_session_nodes_status
ON session_nodes(status, session_key)
WHERE status IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_agent_session_nodes_archived_at
ON session_nodes(archived_at, session_key)
WHERE archived_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_agent_session_nodes_current_session_id
ON session_nodes(current_session_id);
CREATE INDEX IF NOT EXISTS idx_agent_session_nodes_entry_valid_pending
ON session_nodes(session_key)
WHERE entry_valid = 0;
CREATE TABLE IF NOT EXISTS session_key_contract (
id INTEGER NOT NULL PRIMARY KEY CHECK (id = 1),
main_key TEXT NOT NULL,
updated_at INTEGER NOT NULL
) STRICT;
INSERT OR IGNORE INTO session_key_contract (id, main_key, updated_at) VALUES (1, 'main', 0);
CREATE TRIGGER IF NOT EXISTS session_nodes_entry_valid_after_insert
AFTER INSERT ON session_nodes
BEGIN
UPDATE session_nodes SET entry_valid = 0 WHERE session_key = NEW.session_key;
END;
CREATE TRIGGER IF NOT EXISTS session_nodes_entry_valid_after_entry_update
AFTER UPDATE OF entry_json ON session_nodes
BEGIN
UPDATE session_nodes SET entry_valid = 0 WHERE session_key = NEW.session_key;
END;
CREATE TRIGGER IF NOT EXISTS session_nodes_entry_valid_after_identity_update
AFTER UPDATE OF current_session_id, updated_at ON session_nodes
BEGIN
UPDATE session_nodes SET entry_valid = 0 WHERE session_key = NEW.session_key;
END;
CREATE TABLE IF NOT EXISTS session_windows (
session_id TEXT NOT NULL PRIMARY KEY,
session_key TEXT NOT NULL,
previous_session_id TEXT,
reason TEXT CHECK (reason IS NULL OR reason IN ('initial', 'reset', 'rollover', 'fork', 'rewind', 'switch', 'recovery', 'compaction')),
session_scope TEXT NOT NULL DEFAULT 'conversation' CHECK (session_scope IN ('conversation', 'shared-main', 'group', 'channel')),
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
transcript_updated_at INTEGER DEFAULT NULL,
transcript_observed_at INTEGER DEFAULT NULL,
session_entry_provenance INTEGER NOT NULL DEFAULT 0 CHECK (session_entry_provenance IN (0, 1)),
acp_owned INTEGER NOT NULL DEFAULT 0 CHECK (acp_owned IN (0, 1)),
plugin_owner_id TEXT,
hook_external_content_source TEXT CHECK (hook_external_content_source IS NULL OR hook_external_content_source IN ('gmail', 'webhook')),
started_at INTEGER,
ended_at INTEGER,
status TEXT CHECK (status IS NULL OR status IN ('running', 'done', 'failed', 'killed', 'timeout')),
chat_type TEXT CHECK (chat_type IS NULL OR chat_type IN ('direct', 'group', 'channel')),
channel TEXT,
account_id TEXT,
primary_conversation_id TEXT,
model_provider TEXT,
model TEXT,
agent_harness_id TEXT,
parent_session_key TEXT,
spawned_by TEXT,
display_name TEXT,
FOREIGN KEY (session_key) REFERENCES session_nodes(session_key) ON DELETE CASCADE,
FOREIGN KEY (primary_conversation_id) REFERENCES conversations(conversation_id) ON DELETE SET NULL
) STRICT;
CREATE INDEX IF NOT EXISTS idx_agent_session_windows_updated_at
ON session_windows(updated_at DESC, session_id);
CREATE INDEX IF NOT EXISTS idx_agent_session_windows_created_at
ON session_windows(created_at DESC, session_id);
CREATE INDEX IF NOT EXISTS idx_agent_session_windows_conversation
ON session_windows(primary_conversation_id, updated_at DESC, session_id)
WHERE primary_conversation_id IS NOT NULL;
CREATE TABLE IF NOT EXISTS conversations (
conversation_id TEXT NOT NULL PRIMARY KEY,
channel TEXT NOT NULL,
account_id TEXT NOT NULL,
kind TEXT NOT NULL CHECK (kind IN ('direct', 'group', 'channel')),
peer_id TEXT NOT NULL,
delivery_target TEXT NOT NULL,
parent_conversation_id TEXT,
thread_id TEXT,
native_channel_id TEXT,
native_direct_user_id TEXT,
label TEXT,
metadata_json TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
) STRICT;
CREATE INDEX IF NOT EXISTS idx_agent_conversations_lookup
ON conversations(channel, account_id, kind, peer_id, thread_id);
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_conversations_identity
ON conversations(
channel,
account_id,
kind,
peer_id,
IFNULL(parent_conversation_id, ''),
IFNULL(thread_id, '')
);
CREATE INDEX IF NOT EXISTS idx_agent_conversations_updated
ON conversations(updated_at DESC, conversation_id);
CREATE TABLE IF NOT EXISTS conversation_deliveries (
operation_id TEXT NOT NULL PRIMARY KEY,
operation_kind TEXT NOT NULL CHECK (operation_kind IN ('send', 'turn')),
conversation_id TEXT NOT NULL,
source_session_key TEXT,
message_hash TEXT NOT NULL,
status TEXT NOT NULL CHECK (status IN ('created', 'queued', 'sent', 'suppressed', 'rejected', 'unknown', 'replied')),
prepared_message_id TEXT,
platform_message_id TEXT,
queue_id TEXT,
rejection_error TEXT,
reply_message_id TEXT,
reply_to_id TEXT,
reply_thread_id TEXT,
reply_text TEXT,
reply_timestamp INTEGER,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
CHECK (
(status = 'rejected' AND rejection_error IS NOT NULL) OR
(status != 'rejected' AND rejection_error IS NULL)
),
FOREIGN KEY (conversation_id) REFERENCES conversations(conversation_id) ON DELETE CASCADE
) STRICT;
CREATE INDEX IF NOT EXISTS idx_agent_conversation_deliveries_reply
ON conversation_deliveries(conversation_id, platform_message_id, prepared_message_id)
WHERE status IN ('queued', 'sent', 'replied');
CREATE INDEX IF NOT EXISTS idx_agent_conversation_deliveries_updated
ON conversation_deliveries(updated_at DESC, operation_id);
CREATE TABLE IF NOT EXISTS session_conversations (
session_id TEXT NOT NULL,
conversation_id TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'primary' CHECK (role IN ('primary', 'participant', 'related')),
first_seen_at INTEGER NOT NULL,
last_seen_at INTEGER NOT NULL,
PRIMARY KEY (session_id, conversation_id, role),
FOREIGN KEY (session_id) REFERENCES "session_windows"(session_id) ON DELETE CASCADE,
FOREIGN KEY (conversation_id) REFERENCES conversations(conversation_id) ON DELETE CASCADE
) STRICT;
CREATE INDEX IF NOT EXISTS idx_agent_session_conversations_conversation
ON session_conversations(conversation_id, last_seen_at DESC, session_id);
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_session_conversations_primary
ON session_conversations(session_id)
WHERE role = 'primary';
CREATE TABLE IF NOT EXISTS session_members (
session_key TEXT NOT NULL,
identity_id TEXT NOT NULL,
added_by TEXT NOT NULL,
added_at INTEGER NOT NULL,
PRIMARY KEY (session_key, identity_id),
FOREIGN KEY (session_key) REFERENCES session_nodes(session_key) ON DELETE CASCADE
) STRICT;
CREATE INDEX IF NOT EXISTS idx_agent_session_members_identity
ON session_members(identity_id, session_key);
CREATE TABLE IF NOT EXISTS session_suggestions (
id TEXT PRIMARY KEY,
session_key TEXT NOT NULL,
author_id TEXT NOT NULL,
author_label TEXT,
text TEXT NOT NULL,
created_at INTEGER NOT NULL,
state TEXT NOT NULL CHECK (state IN ('pending', 'accepted', 'dismissed')),
dispatch_token TEXT,
dispatch_started_at INTEGER,
dispatch_resolution TEXT CHECK (dispatch_resolution IN ('send', 'queue', 'edit', 'dismiss')),
CHECK (
(dispatch_token IS NULL AND dispatch_started_at IS NULL AND dispatch_resolution IS NULL)
OR (dispatch_token IS NOT NULL AND dispatch_started_at IS NOT NULL AND dispatch_resolution IS NOT NULL)
),
FOREIGN KEY (session_key) REFERENCES session_nodes(session_key) ON DELETE CASCADE
) STRICT;
CREATE INDEX IF NOT EXISTS idx_agent_session_suggestions_session_state_created
ON session_suggestions(session_key, state, created_at, id);
CREATE INDEX IF NOT EXISTS idx_agent_session_suggestions_author_created
ON session_suggestions(author_id, created_at, id);
CREATE TABLE IF NOT EXISTS board_tabs (
session_key TEXT NOT NULL,
tab_id TEXT NOT NULL,
title TEXT NOT NULL,
position INTEGER NOT NULL CHECK (position >= 0),
chat_dock TEXT NOT NULL DEFAULT 'right' CHECK (chat_dock IN ('left', 'right', 'bottom', 'hidden')),
created_by TEXT NOT NULL CHECK (created_by IN ('user', 'agent')),
revision INTEGER NOT NULL CHECK (revision >= 0),
PRIMARY KEY (session_key, tab_id),
FOREIGN KEY (session_key) REFERENCES session_nodes(session_key) ON DELETE CASCADE
) STRICT;
CREATE TABLE IF NOT EXISTS board_widgets (
session_key TEXT NOT NULL,
name TEXT NOT NULL,
tab_id TEXT NOT NULL,
title TEXT,
content_kind TEXT NOT NULL CHECK (content_kind IN ('html', 'mcp-app', 'plugin')),
html BLOB,
descriptor_json TEXT,
sha256 TEXT NOT NULL,
view_generation TEXT,
revision INTEGER NOT NULL CHECK (revision >= 1),
size_w INTEGER NOT NULL CHECK (size_w BETWEEN 1 AND 12),
size_h INTEGER NOT NULL CHECK (size_h BETWEEN 1 AND 20),
position INTEGER NOT NULL CHECK (position >= 0),
manifest TEXT NOT NULL DEFAULT '{}',
grant_state TEXT NOT NULL DEFAULT 'none' CHECK (grant_state IN ('none', 'pending', 'granted', 'rejected')),
granted_sha TEXT,
created_by TEXT NOT NULL CHECK (created_by IN ('user', 'agent')),
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
PRIMARY KEY (session_key, name),
FOREIGN KEY (session_key, tab_id) REFERENCES board_tabs(session_key, tab_id) ON DELETE CASCADE,
CHECK (
(content_kind = 'html' AND html IS NOT NULL AND descriptor_json IS NULL AND view_generation IS NOT NULL) OR
(content_kind = 'mcp-app' AND html IS NULL AND descriptor_json IS NOT NULL AND view_generation IS NULL) OR
(content_kind = 'plugin' AND html IS NULL AND descriptor_json IS NOT NULL AND view_generation IS NULL)
)
) STRICT;
CREATE INDEX IF NOT EXISTS idx_agent_board_widgets_tab_position
ON board_widgets(session_key, tab_id, position);
CREATE TABLE IF NOT EXISTS heartbeat_outcomes (
session_key TEXT NOT NULL PRIMARY KEY,
run_session_key TEXT NOT NULL,
outcome TEXT NOT NULL CHECK (outcome IN ('progress', 'done', 'blocked', 'needs_attention')),
summary TEXT NOT NULL,
response_reason TEXT,
priority TEXT CHECK (priority IS NULL OR priority IN ('low', 'normal', 'high')),
next_check TEXT,
task_names_json TEXT,
wake_source TEXT,
wake_reason TEXT,
occurred_at INTEGER NOT NULL,
context_run_id TEXT,
context_claimed_at INTEGER,
updated_at INTEGER NOT NULL,
FOREIGN KEY (session_key) REFERENCES session_nodes(session_key) ON DELETE CASCADE
) STRICT;
CREATE TABLE IF NOT EXISTS transcript_events (
session_id TEXT NOT NULL,
seq INTEGER NOT NULL,
event_json TEXT NOT NULL,
created_at INTEGER NOT NULL,
PRIMARY KEY (session_id, seq),
FOREIGN KEY (session_id) REFERENCES "session_windows"(session_id) ON DELETE CASCADE
) STRICT;
CREATE TABLE IF NOT EXISTS transcript_rewrite_watermarks (
session_id TEXT NOT NULL PRIMARY KEY,
generation TEXT NOT NULL,
updated_at INTEGER NOT NULL,
FOREIGN KEY (session_id) REFERENCES "session_windows"(session_id) ON DELETE CASCADE
) STRICT;
CREATE TABLE IF NOT EXISTS trajectory_runtime_events (
session_id TEXT NOT NULL,
seq INTEGER NOT NULL,
run_id TEXT,
event_json TEXT NOT NULL,
created_at INTEGER NOT NULL,
PRIMARY KEY (session_id, seq),
FOREIGN KEY (session_id) REFERENCES "session_windows"(session_id) ON DELETE CASCADE
) STRICT;
CREATE INDEX IF NOT EXISTS idx_agent_trajectory_runtime_run
ON trajectory_runtime_events(session_id, run_id, seq)
WHERE run_id IS NOT NULL;
CREATE TABLE IF NOT EXISTS acp_parent_stream_events (
session_id TEXT NOT NULL,
run_id TEXT NOT NULL,
seq INTEGER NOT NULL,
event_json TEXT NOT NULL,
created_at INTEGER NOT NULL,
PRIMARY KEY (session_id, run_id, seq),
FOREIGN KEY (session_id) REFERENCES "session_windows"(session_id) ON DELETE CASCADE
) STRICT;
CREATE INDEX IF NOT EXISTS idx_agent_acp_parent_stream_run
ON acp_parent_stream_events(run_id, seq);
CREATE TABLE IF NOT EXISTS transcript_event_identities (
session_id TEXT NOT NULL,
event_id TEXT NOT NULL,
seq INTEGER NOT NULL,
event_type TEXT,
parent_id TEXT,
message_idempotency_key TEXT,
created_at INTEGER NOT NULL,
PRIMARY KEY (session_id, event_id),
FOREIGN KEY (session_id, seq) REFERENCES transcript_events(session_id, seq) ON DELETE CASCADE
) STRICT;
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_transcript_message_idempotency
ON transcript_event_identities(session_id, message_idempotency_key)
WHERE message_idempotency_key IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_agent_transcript_event_parent
ON transcript_event_identities(session_id, parent_id)
WHERE parent_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_agent_transcript_event_sequence
ON transcript_event_identities(session_id, event_type, seq DESC);
CREATE TABLE IF NOT EXISTS cache_entries (
scope TEXT NOT NULL,
key TEXT NOT NULL,
value_json TEXT,
blob BLOB,
expires_at INTEGER,
updated_at INTEGER NOT NULL,
PRIMARY KEY (scope, key)
) STRICT;
CREATE INDEX IF NOT EXISTS idx_agent_cache_expiry
ON cache_entries(scope, expires_at, key)
WHERE expires_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_agent_cache_updated
ON cache_entries(scope, updated_at DESC, key);
CREATE TABLE IF NOT EXISTS auth_profile_store (
store_key TEXT NOT NULL PRIMARY KEY,
store_json TEXT NOT NULL,
updated_at INTEGER NOT NULL
) STRICT;
CREATE TABLE IF NOT EXISTS auth_profile_state (
state_key TEXT NOT NULL PRIMARY KEY,
state_json TEXT NOT NULL,
updated_at INTEGER NOT NULL
) STRICT;
CREATE TABLE IF NOT EXISTS memory_index_meta (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
) STRICT;
CREATE TABLE IF NOT EXISTS memory_index_sources (
id INTEGER PRIMARY KEY,
path TEXT NOT NULL,
source TEXT NOT NULL DEFAULT 'memory',
hash TEXT NOT NULL,
mtime REAL NOT NULL,
size INTEGER NOT NULL,
UNIQUE (path, source)
) STRICT;
CREATE TABLE IF NOT EXISTS memory_index_chunks (
id TEXT PRIMARY KEY,
path TEXT NOT NULL,
source TEXT NOT NULL DEFAULT 'memory',
start_line INTEGER NOT NULL,
end_line INTEGER NOT NULL,
hash TEXT NOT NULL,
model TEXT NOT NULL,
text TEXT NOT NULL,
embedding TEXT NOT NULL,
updated_at INTEGER NOT NULL
) STRICT;
CREATE TABLE IF NOT EXISTS memory_index_chunk_recall_metadata (
chunk_id TEXT PRIMARY KEY,
importance INTEGER CHECK (importance IS NULL OR importance BETWEEN 1 AND 10),
triggers TEXT,
project_key TEXT,
FOREIGN KEY (chunk_id) REFERENCES memory_index_chunks(id) ON DELETE CASCADE
) STRICT;
CREATE TABLE IF NOT EXISTS memory_index_chunk_provenance (
chunk_id TEXT PRIMARY KEY,
origin_class TEXT NOT NULL CHECK (origin_class IN ('owner', 'agent', 'untrusted', 'system')),
session_kind TEXT NOT NULL CHECK (session_kind IN ('interactive', 'cron', 'heartbeat', 'subagent', 'unknown')),
observed_at INTEGER NOT NULL,
supersedes_key TEXT,
FOREIGN KEY (chunk_id) REFERENCES memory_index_chunks(id) ON DELETE CASCADE
) STRICT;
CREATE TABLE IF NOT EXISTS memory_embedding_cache (
provider TEXT NOT NULL,
model TEXT NOT NULL,
provider_key TEXT NOT NULL,
hash TEXT NOT NULL,
embedding TEXT NOT NULL,
dims INTEGER,
updated_at INTEGER NOT NULL,
PRIMARY KEY (provider, model, provider_key, hash)
) STRICT;
CREATE TABLE IF NOT EXISTS memory_index_state (
id INTEGER PRIMARY KEY CHECK (id = 1),
revision INTEGER NOT NULL
) STRICT;
CREATE TABLE IF NOT EXISTS standing_intents (
intent_key INTEGER PRIMARY KEY,
id TEXT NOT NULL UNIQUE,
description TEXT NOT NULL,
trigger_keywords TEXT NOT NULL,
trigger_embedding TEXT,
channel_scope TEXT,
sender_scope TEXT,
creator_sender TEXT CHECK (creator_sender IS NULL OR length(trim(creator_sender)) > 0),
status TEXT NOT NULL CHECK (status IN ('pending', 'armed', 'fired', 'done', 'cancelled', 'expired')),
expires_at INTEGER NOT NULL,
max_fires INTEGER NOT NULL CHECK (max_fires > 0),
fire_count INTEGER NOT NULL DEFAULT 0 CHECK (fire_count >= 0),
cooldown_seconds INTEGER NOT NULL DEFAULT 86400 CHECK (cooldown_seconds >= 0),
last_fired_at INTEGER,
created_at INTEGER NOT NULL,
source_session_id TEXT
) STRICT;
CREATE INDEX IF NOT EXISTS idx_standing_intents_lifecycle
ON standing_intents(status, expires_at, last_fired_at);
CREATE INDEX IF NOT EXISTS idx_standing_intents_scope
ON standing_intents(status, channel_scope, sender_scope);
CREATE VIRTUAL TABLE IF NOT EXISTS standing_intents_fts USING fts5(
trigger_keywords,
content = 'standing_intents',
content_rowid = 'intent_key',
tokenize = 'unicode61 remove_diacritics 2'
);
CREATE TRIGGER IF NOT EXISTS standing_intents_fts_after_insert
AFTER INSERT ON standing_intents
BEGIN
INSERT INTO standing_intents_fts(rowid, trigger_keywords)
VALUES (new.intent_key, new.trigger_keywords);
END;
CREATE TRIGGER IF NOT EXISTS standing_intents_fts_after_delete
AFTER DELETE ON standing_intents
BEGIN
INSERT INTO standing_intents_fts(standing_intents_fts, rowid, trigger_keywords)
VALUES ('delete', old.intent_key, old.trigger_keywords);
END;
CREATE TRIGGER IF NOT EXISTS standing_intents_fts_after_update
AFTER UPDATE OF trigger_keywords ON standing_intents
BEGIN
INSERT INTO standing_intents_fts(standing_intents_fts, rowid, trigger_keywords)
VALUES ('delete', old.intent_key, old.trigger_keywords);
INSERT INTO standing_intents_fts(rowid, trigger_keywords)
VALUES (new.intent_key, new.trigger_keywords);
END;
CREATE TABLE IF NOT EXISTS session_transcript_index_state (
session_id TEXT NOT NULL PRIMARY KEY,
indexed_seq INTEGER NOT NULL,
leaf_event_id TEXT,
needs_rebuild INTEGER NOT NULL DEFAULT 0,
active_event_count INTEGER NOT NULL DEFAULT 0,
active_message_count INTEGER NOT NULL DEFAULT 0,
updated_at INTEGER NOT NULL,
FOREIGN KEY (session_id) REFERENCES session_windows(session_id) ON DELETE CASCADE
) STRICT;
CREATE TABLE IF NOT EXISTS session_transcript_active_events (
session_id TEXT NOT NULL,
active_position INTEGER NOT NULL CHECK (active_position >= 0),
event_seq INTEGER NOT NULL,
message_position INTEGER CHECK (message_position IS NULL OR message_position >= 0),
PRIMARY KEY (session_id, active_position),
FOREIGN KEY (session_id, event_seq) REFERENCES transcript_events(session_id, seq) ON DELETE CASCADE
) STRICT;
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_transcript_active_event_seq
ON session_transcript_active_events(session_id, event_seq);
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_transcript_active_messages
ON session_transcript_active_events(session_id, message_position)
WHERE message_position IS NOT NULL;
CREATE VIRTUAL TABLE IF NOT EXISTS session_transcript_fts USING fts5(
text,
session_id UNINDEXED,
message_id UNINDEXED,
role UNINDEXED,
timestamp UNINDEXED,
tokenize = 'unicode61 remove_diacritics 2'
);
INSERT OR IGNORE INTO memory_index_state (id, revision) VALUES (1, 0);
CREATE TRIGGER IF NOT EXISTS memory_index_sources_revision_after_insert
AFTER INSERT ON memory_index_sources
BEGIN
UPDATE memory_index_state SET revision = revision + 1 WHERE id = 1;
END;
CREATE TRIGGER IF NOT EXISTS memory_index_sources_revision_after_update
AFTER UPDATE ON memory_index_sources
BEGIN
UPDATE memory_index_state SET revision = revision + 1 WHERE id = 1;
END;
CREATE TRIGGER IF NOT EXISTS memory_index_sources_revision_after_delete
AFTER DELETE ON memory_index_sources
BEGIN
UPDATE memory_index_state SET revision = revision + 1 WHERE id = 1;
END;
CREATE TRIGGER IF NOT EXISTS memory_index_chunks_revision_after_insert
AFTER INSERT ON memory_index_chunks
BEGIN
UPDATE memory_index_state SET revision = revision + 1 WHERE id = 1;
END;
CREATE TRIGGER IF NOT EXISTS memory_index_chunks_revision_after_update
AFTER UPDATE ON memory_index_chunks
BEGIN
UPDATE memory_index_state SET revision = revision + 1 WHERE id = 1;
END;
CREATE TRIGGER IF NOT EXISTS memory_index_chunks_revision_after_delete
AFTER DELETE ON memory_index_chunks
BEGIN
UPDATE memory_index_state SET revision = revision + 1 WHERE id = 1;
END;
CREATE INDEX IF NOT EXISTS idx_memory_embedding_cache_updated_at
ON memory_embedding_cache(updated_at);
CREATE INDEX IF NOT EXISTS idx_memory_index_sources_source
ON memory_index_sources(source);
CREATE INDEX IF NOT EXISTS idx_memory_index_chunks_path_source
ON memory_index_chunks(path, source);
CREATE INDEX IF NOT EXISTS idx_memory_index_chunks_path
ON memory_index_chunks(path);
CREATE INDEX IF NOT EXISTS idx_memory_index_chunks_source
ON memory_index_chunks(source);\n`;

View File

@@ -0,0 +1,7 @@
import { fileURLToPath } from "node:url";
// Use the native builtin so unrelated node:fs mocks cannot replace this source input.
// Production builds replace this module so packaged database opens need no asset file.
export const OPENCLAW_AGENT_SCHEMA_SQL = process
.getBuiltinModule("node:fs")
.readFileSync(fileURLToPath(new URL("./openclaw-agent-schema.sql", import.meta.url)), "utf8");

View File

@@ -10,7 +10,7 @@ import { MEMORY_INDEX_CHUNK_RECALL_METADATA_TABLE } from "../../packages/memory-
import { openNodeSqliteDatabase } from "../infra/node-sqlite.js";
import { OPENCLAW_AGENT_SCHEMA_VERSION } from "./openclaw-agent-db-contract.js";
import { ensureOpenClawAgentDatabaseSchema } from "./openclaw-agent-db-schema.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.generated.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.js";
import { ensureOpenClawAgentStandingIntentsSchema } from "./openclaw-agent-standing-intents-schema.js";
const STANDING_SCHEMA_START = "CREATE TABLE IF NOT EXISTS standing_intents (";

View File

@@ -1,6 +1,6 @@
import type { DatabaseSync } from "node:sqlite";
import { runSqliteImmediateTransactionSync } from "../infra/sqlite-transaction.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.generated.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.js";
export const STANDING_INTENTS_TABLE = "standing_intents";
export const STANDING_INTENTS_FTS_TABLE = "standing_intents_fts";

View File

@@ -5,12 +5,12 @@ import {
assertOpenClawAgentDatabaseForMaintenance,
OPENCLAW_AGENT_SCHEMA_VERSION,
} from "./openclaw-agent-db.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.generated.js";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.js";
import {
assertOpenClawStateDatabaseForMaintenance,
OPENCLAW_STATE_SCHEMA_VERSION,
} from "./openclaw-state-db.js";
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.generated.js";
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.js";
describe("OpenClaw database maintenance schema validation", () => {
it("accepts the current global and agent schemas", () => {

View File

@@ -16,7 +16,7 @@ import {
type OpenClawStateDatabaseOptions,
} from "./openclaw-state-db-contract.js";
import { resolveOpenClawStateSqlitePath } from "./openclaw-state-db.paths.js";
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.generated.js";
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.js";
const OPENCLAW_STATE_MAINTENANCE_SCHEMA_COMPATIBILITY = {
allowedMissingTables: LAZY_ADDITIVE_STATE_TABLES,

View File

@@ -5,7 +5,7 @@ import {
assertCanonicalOperatorApprovalKinds,
repairOperatorApprovalSchema,
} from "./openclaw-state-db-operator-approval-migration.js";
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.generated.js";
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.js";
function canonicalOperatorApprovalCreateSql(): string {
const marker = "CREATE TABLE IF NOT EXISTS operator_approvals (";

View File

@@ -2,7 +2,7 @@
import type { DatabaseSync } from "node:sqlite";
import { runSqliteImmediateTransactionSync } from "../infra/sqlite-transaction.js";
import { tableExists } from "./openclaw-state-db-schema-helpers.js";
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.generated.js";
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.js";
const COLUMNS = [
"approval_id",

View File

@@ -74,7 +74,7 @@ import {
} from "./openclaw-state-db-schema-repair.js";
import * as sessionWatchMigration from "./openclaw-state-db-session-watch-migration.js";
import type { DB as OpenClawStateKyselyDatabase } from "./openclaw-state-db.generated.js";
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.generated.js";
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.js";
export {
OPENCLAW_DATABASE_SCHEMA_DOCS_URL,

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
import { fileURLToPath } from "node:url";
// Use the native builtin so unrelated node:fs mocks cannot replace this source input.
// Production builds replace this module so packaged database opens need no asset file.
export const OPENCLAW_STATE_SCHEMA_SQL = process
.getBuiltinModule("node:fs")
.readFileSync(fileURLToPath(new URL("./openclaw-state-schema.sql", import.meta.url)), "utf8");

View File

@@ -387,6 +387,14 @@ describe("resolveBuildAllSteps", () => {
);
}
expect(unified.cache?.env).toContain("OPENCLAW_BUILD_PRIVATE_QA");
expect(unified.cache?.inputs).toEqual(
expect.arrayContaining([
expect.objectContaining({
path: "src",
extensions: expect.arrayContaining([".sql"]),
}),
]),
);
expect(resolveBuildAllStepOnCacheHit(getBuildAllStep("copy-export-html-templates"))).toBeNull();
});

View File

@@ -46,6 +46,43 @@ const env = {
const OUTPUT_SOURCE_MAPS = process.env.OUTPUT_SOURCE_MAPS === "1";
const RUN_NODE_SKIP_DTS_BUILD = process.env.OPENCLAW_RUN_NODE_SKIP_DTS_BUILD === "1";
const TSDOWN_DECLARATIONS = !RUN_NODE_SKIP_DTS_BUILD;
export const STATE_SCHEMA_INLINE_PLUGIN_NAME = "openclaw:inline-state-schemas";
const STATE_SCHEMA_MODULES = [
{
modulePath: "src/state/openclaw-state-schema.ts",
schemaPath: "src/state/openclaw-state-schema.sql",
exportName: "OPENCLAW_STATE_SCHEMA_SQL",
},
{
modulePath: "src/state/openclaw-agent-schema.ts",
schemaPath: "src/state/openclaw-agent-schema.sql",
exportName: "OPENCLAW_AGENT_SCHEMA_SQL",
},
] as const;
/** Inline canonical schema bytes so packaged database opens need no SQL asset. */
export function createStateSchemaInlinePlugin(rootDir: string = process.cwd()) {
const schemasByModulePath = new Map(
STATE_SCHEMA_MODULES.map((schema) => [path.resolve(rootDir, schema.modulePath), schema]),
);
return {
name: STATE_SCHEMA_INLINE_PLUGIN_NAME,
load(this: { addWatchFile(id: string): void }, id: string) {
const schema = schemasByModulePath.get(path.resolve(id));
if (!schema) {
return null;
}
const schemaPath = path.resolve(rootDir, schema.schemaPath);
this.addWatchFile(schemaPath);
return {
code: `export const ${schema.exportName} = ${JSON.stringify(fs.readFileSync(schemaPath, "utf8"))};\n`,
moduleType: "js" as const,
};
},
};
}
const SUPPRESSED_EVAL_WARNING_PATHS = [
"@protobufjs/inquire/index.js",
@@ -680,6 +717,7 @@ const configs = [
// and bundled hooks in one graph so runtime singletons are emitted once.
entry: unifiedDistEntries,
deps: unifiedDeps,
plugins: [createStateSchemaInlinePlugin()],
},
false,
),