run-dmcp 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +76 -10
  2. package/dist/bin/run-dmcp.d.ts +2 -0
  3. package/dist/bin/run-dmcp.js +55 -0
  4. package/dist/db/connection.d.ts +32 -0
  5. package/dist/db/connection.js +38 -16
  6. package/dist/db/schema.d.ts +29 -1
  7. package/dist/db/schema.js +439 -7
  8. package/dist/http/server.js +3 -3
  9. package/dist/index.d.ts +36 -2
  10. package/dist/index.js +184 -92
  11. package/dist/mcp-server.d.ts +49 -0
  12. package/dist/mcp-server.js +127 -0
  13. package/dist/reader/turnReader.d.ts +185 -0
  14. package/dist/reader/turnReader.js +288 -0
  15. package/dist/register/batch.js +5 -79
  16. package/dist/register/mcp-resources.d.ts +9 -0
  17. package/dist/register/mcp-resources.js +16 -62
  18. package/dist/register/render.d.ts +17 -0
  19. package/dist/register/render.js +50 -0
  20. package/dist/register/resolve.d.ts +14 -0
  21. package/dist/register/resolve.js +102 -0
  22. package/dist/register/resources.js +11 -4
  23. package/dist/register/timeline.d.ts +2 -0
  24. package/dist/register/timeline.js +311 -0
  25. package/dist/rpg/index.d.ts +29 -0
  26. package/dist/rpg/index.js +55 -0
  27. package/dist/rpg/register/abilities.d.ts +2 -0
  28. package/dist/rpg/register/abilities.js +165 -0
  29. package/dist/rpg/register/batch.d.ts +2 -0
  30. package/dist/rpg/register/batch.js +92 -0
  31. package/dist/rpg/register/combat.d.ts +2 -0
  32. package/dist/rpg/register/combat.js +207 -0
  33. package/dist/rpg/register/mcp-prompts.d.ts +2 -0
  34. package/dist/rpg/register/mcp-prompts.js +684 -0
  35. package/dist/rpg/register/mcp-resources.d.ts +2 -0
  36. package/dist/rpg/register/mcp-resources.js +61 -0
  37. package/dist/rpg/register/quests.d.ts +2 -0
  38. package/dist/rpg/register/quests.js +118 -0
  39. package/dist/rpg/register/status.d.ts +2 -0
  40. package/dist/rpg/register/status.js +130 -0
  41. package/dist/rpg/register/tables.d.ts +2 -0
  42. package/dist/rpg/register/tables.js +146 -0
  43. package/dist/rpg/tools/ability.d.ts +48 -0
  44. package/dist/rpg/tools/ability.js +238 -0
  45. package/dist/rpg/tools/combat.d.ts +13 -0
  46. package/dist/rpg/tools/combat.js +195 -0
  47. package/dist/rpg/tools/dice.d.ts +23 -0
  48. package/dist/rpg/tools/dice.js +111 -0
  49. package/dist/rpg/tools/quest.d.ts +34 -0
  50. package/dist/rpg/tools/quest.js +164 -0
  51. package/dist/rpg/tools/status.d.ts +36 -0
  52. package/dist/rpg/tools/status.js +218 -0
  53. package/dist/rpg/tools/tables.d.ts +33 -0
  54. package/dist/rpg/tools/tables.js +209 -0
  55. package/dist/schemas/index.d.ts +12 -12
  56. package/dist/timeline/adjudication.d.ts +150 -0
  57. package/dist/timeline/adjudication.js +174 -0
  58. package/dist/timeline/changes.d.ts +100 -0
  59. package/dist/timeline/changes.js +161 -0
  60. package/dist/timeline/checkpoint.d.ts +69 -0
  61. package/dist/timeline/checkpoint.js +131 -0
  62. package/dist/timeline/clock.d.ts +89 -0
  63. package/dist/timeline/clock.js +173 -0
  64. package/dist/timeline/constrained.d.ts +220 -0
  65. package/dist/timeline/constrained.js +671 -0
  66. package/dist/timeline/export.d.ts +171 -0
  67. package/dist/timeline/export.js +329 -0
  68. package/dist/timeline/irreversible.d.ts +85 -0
  69. package/dist/timeline/irreversible.js +108 -0
  70. package/dist/timeline/kinds.d.ts +14 -0
  71. package/dist/timeline/kinds.js +22 -0
  72. package/dist/timeline/narration.d.ts +175 -0
  73. package/dist/timeline/narration.js +259 -0
  74. package/dist/timeline/projection.d.ts +97 -0
  75. package/dist/timeline/projection.js +330 -0
  76. package/dist/timeline/provenance.d.ts +66 -0
  77. package/dist/timeline/provenance.js +45 -0
  78. package/dist/timeline/registry.d.ts +95 -0
  79. package/dist/timeline/registry.js +124 -0
  80. package/dist/timeline/render.d.ts +121 -0
  81. package/dist/timeline/render.js +187 -0
  82. package/dist/timeline/replay.d.ts +64 -0
  83. package/dist/timeline/replay.js +104 -0
  84. package/dist/timeline/resolve.d.ts +262 -0
  85. package/dist/timeline/resolve.js +226 -0
  86. package/dist/timeline/schema.d.ts +13 -0
  87. package/dist/timeline/schema.js +262 -0
  88. package/dist/timeline/t.d.ts +80 -0
  89. package/dist/timeline/t.js +37 -0
  90. package/dist/tools/constraint.d.ts +44 -80
  91. package/dist/tools/constraint.js +115 -124
  92. package/dist/tools/relationship.d.ts +83 -2
  93. package/dist/tools/relationship.js +139 -62
  94. package/dist/tools/resource.d.ts +31 -6
  95. package/dist/tools/resource.js +106 -153
  96. package/dist/types/index.d.ts +19 -1
  97. package/dist/utils/output-schemas.d.ts +593 -2
  98. package/dist/utils/output-schemas.js +3 -0
  99. package/dist/utils/webui.d.ts +32 -0
  100. package/dist/utils/webui.js +54 -1
  101. package/package.json +20 -4
  102. package/dist/__tests__/engineVocabulary.test.d.ts +0 -1
  103. package/dist/__tests__/engineVocabulary.test.js +0 -147
  104. package/dist/db/__tests__/connection.test.d.ts +0 -1
  105. package/dist/db/__tests__/connection.test.js +0 -72
  106. package/dist/db/__tests__/testDb.d.ts +0 -33
  107. package/dist/db/__tests__/testDb.js +0 -41
  108. package/dist/test-setup.d.ts +0 -1
  109. package/dist/test-setup.js +0 -13
  110. package/dist/tools/__tests__/audio.test.d.ts +0 -1
  111. package/dist/tools/__tests__/audio.test.js +0 -59
  112. package/dist/tools/__tests__/conserved.test.d.ts +0 -1
  113. package/dist/tools/__tests__/conserved.test.js +0 -488
  114. package/dist/tools/__tests__/constraint.test.d.ts +0 -1
  115. package/dist/tools/__tests__/constraint.test.js +0 -212
  116. package/dist/tools/__tests__/expiry-consequences.test.d.ts +0 -1
  117. package/dist/tools/__tests__/expiry-consequences.test.js +0 -110
  118. package/dist/tools/__tests__/images.test.d.ts +0 -1
  119. package/dist/tools/__tests__/images.test.js +0 -59
  120. package/dist/tools/__tests__/relationship.test.d.ts +0 -1
  121. package/dist/tools/__tests__/relationship.test.js +0 -132
  122. package/dist/tools/__tests__/resource-constraints.test.d.ts +0 -1
  123. package/dist/tools/__tests__/resource-constraints.test.js +0 -131
  124. package/dist/tools/__tests__/resource.test.d.ts +0 -1
  125. package/dist/tools/__tests__/resource.test.js +0 -190
  126. package/dist/tools/__tests__/time.test.d.ts +0 -1
  127. package/dist/tools/__tests__/time.test.js +0 -404
  128. package/dist/tools/__tests__/timers.test.d.ts +0 -1
  129. package/dist/tools/__tests__/timers.test.js +0 -426
  130. package/dist/tools/__tests__/world.test.d.ts +0 -1
  131. package/dist/tools/__tests__/world.test.js +0 -70
  132. package/dist/utils/__tests__/json.test.d.ts +0 -1
  133. package/dist/utils/__tests__/json.test.js +0 -55
  134. package/dist/utils/__tests__/validation.test.d.ts +0 -1
  135. package/dist/utils/__tests__/validation.test.js +0 -90
package/README.md CHANGED
@@ -12,21 +12,86 @@ holds full adjudication discretion over a world that only ever stores *now*.
12
12
 
13
13
  ## Status
14
14
 
15
- **0.1.0 — the foundation, not the thesis.** What ships today is the predecessor's engine plus four
15
+ **0.1.1 — the foundation, not the thesis.** What ships today is the predecessor's engine plus four
16
16
  pieces of generic mechanism that were built for it and offered back to it:
17
17
 
18
18
  - **Atomicity** — `withTransaction()` wired into the multi-write operations that were running
19
19
  non-atomically.
20
- - **Declarative constraints** — resources can be declared `bounded`, `monotonic` or `resolve_only`,
21
- and the store enforces it rather than trusting every caller.
20
+ - **Declarative constraints** — resources can be declared `bounded` or `monotonic`, and the store
21
+ enforces it rather than trusting every caller. (`resolve_only` — every direct write rejected, so a
22
+ value can move only through an adjudicating call — exists in a downstream consumer and has not been
23
+ extracted here yet. It arrives with the resolve protocol; see [docs/DESIGN.md](docs/DESIGN.md) §5.2a.)
22
24
  - **Conserved resource sets** — a set of resources can be declared conserved, with an atomic transfer
23
25
  that never silently clamps.
24
26
  - **On-expiry consequences** — scheduled events and timers can carry a consequence that actually
25
27
  lands when they expire, rather than expiring into nothing.
26
28
 
29
+ 0.1.1 adds the packaging half of that: importing the library starts nothing, the database resolves to
30
+ the consuming application rather than into `node_modules`, and a consumer can bring up its own tables
31
+ through the migration hook below.
32
+
27
33
  The timeline that gives this project its reason to exist — interval-versioned facts, `replay(t)`,
28
- irreversibility, `changes_within` — is designed and accepted but **not yet built**. See
29
- [docs/DESIGN.md](docs/DESIGN.md), which is the authority, and §11 for the order things land in.
34
+ irreversibility, timeline export, `changes_within` — is built, and every write of world state appends
35
+ to it in the same transaction, through generated triggers rather than edited write sites.
36
+
37
+ **There is one versioning substrate, and it is the timeline.** A resource's value is a constrained
38
+ numeric fact: declared constraints (`bounded`, `monotonic`, conserved sets, `irreversible`) are
39
+ checked at a single choke point, and "what did this value used to be" is answered by the fact
40
+ intervals rather than by a history table beside them. The former `resource_history` and
41
+ `relationship_history` tables no longer accept writes. See [docs/DESIGN.md](docs/DESIGN.md), which is
42
+ the authority, §5.4 for that decision and §11 for the order the rest lands in.
43
+
44
+ ## Running it, and depending on it
45
+
46
+ These are two different things, and the package keeps them apart.
47
+
48
+ **As an application** — `run-dmcp` (or `node dist/bin/run-dmcp.js`) serves MCP over stdio and the web
49
+ UI alongside it. `DMCP_HTTP_PORT` moves the web UI; `DMCP_NO_HTTP=1` turns it off entirely, which is
50
+ what a host that spawns this as a subprocess wants: a referee has no use for an admin page, and a
51
+ server it cannot close squats a port.
52
+
53
+ **As a dependency** — importing the package starts nothing. No listener, no database file, no work at
54
+ all: the entry point is exports, and the application lives behind `bin`. A consumer decides when the
55
+ schema comes up, where the database lives, and whether anything listens.
56
+
57
+ **The package root is the core, and the tabletop surface is a layer above it.** Dice, combat,
58
+ abilities, status effects, random tables and quests are genuinely game-shaped — an optional
59
+ dependency, not part of the engine (see [docs/DESIGN.md](docs/DESIGN.md) §8). A consumer that only
60
+ needs entities, facts, events and the timeline imports `run-dmcp` and calls `createCoreMcpServer`. A
61
+ consumer that wants the full tabletop surface imports `run-dmcp/rpg` and calls `createMcpServer` —
62
+ same name, same options, the whole assembly this package has always served:
63
+
64
+ ```ts
65
+ import { initializeSchema, type SchemaMigration } from "run-dmcp";
66
+ import { createMcpServer } from "run-dmcp/rpg";
67
+
68
+ const migrations: SchemaMigration[] = [
69
+ {
70
+ name: "my-tables",
71
+ up(db) {
72
+ db.exec(`CREATE TABLE IF NOT EXISTS my_table (id TEXT PRIMARY KEY)`);
73
+ try {
74
+ db.exec(`ALTER TABLE my_table ADD COLUMN added_later TEXT`);
75
+ } catch {
76
+ // Already added -- migrations run on every startup, so they must be idempotent.
77
+ }
78
+ },
79
+ },
80
+ ];
81
+
82
+ initializeSchema({ migrations }); // engine tables first, then yours, one pass, one database
83
+ const server = createMcpServer(); // built, not started -- connect it to a transport yourself
84
+ ```
85
+
86
+ The hook is a parameter rather than a global `register()` because a parameter cannot be registered
87
+ too late to run. There is no framework behind it: no version table, no record of what already ran, no
88
+ down-migrations. Every migration runs on every startup, exactly like the engine's own DDL — which is
89
+ what forces them to be idempotent, and it is tested against an existing database, not just a fresh
90
+ one.
91
+
92
+ The database lands in the consuming application: `DMCP_DB_PATH` if set, else an existing
93
+ `~/.local/share/dmcp`, else `./data/games.db` relative to the working directory. Never inside the
94
+ installed package.
30
95
 
31
96
  ## Provenance
32
97
 
@@ -35,9 +100,10 @@ commit was 2026-01-06. This repository carries its full history rather than a sq
35
100
  first commit here is his, from 2025-12-30.
36
101
 
37
102
  It is **not** a GitHub fork, deliberately — a repository inside another's fork network is discoverable
38
- only as "a fork of" and cannot own its issue tracker. The four offers listed above stand as open pull
39
- requests against the original and remain valid there; they are merged here because this project
40
- cannot wait on a repository that has not moved in seven months.
103
+ only as "a fork of" and cannot own its issue tracker. The four offers listed above were opened as pull
104
+ requests against the original ([#6](https://github.com/shawnrushefsky/dmcp/pull/6)–[#9](https://github.com/shawnrushefsky/dmcp/pull/9))
105
+ and withdrawn on 2026-08-18 after seven months without a maintainer response; they are merged here
106
+ instead. Their diffs remain readable upstream at `refs/pull/6..9/head`.
41
107
 
42
108
  MIT, and his copyright notice travels with the code. See [LICENSE](LICENSE).
43
109
 
@@ -67,12 +133,12 @@ npm run typecheck # tsc --noEmit
67
133
  npm run test:run # vitest, one shot
68
134
  npm run build # tsc + client build
69
135
 
70
- npm run dev # tsx src/index.ts
136
+ npm run dev # tsx src/bin/run-dmcp.ts
71
137
  ```
72
138
 
73
139
  CI runs lint, typecheck, tests and build on every push, for both the server and the client.
74
140
 
75
- Local MCP inspection: `npx @modelcontextprotocol/inspector node dist/index.js`
141
+ Local MCP inspection: `npx @modelcontextprotocol/inspector node dist/bin/run-dmcp.js`
76
142
 
77
143
  ## License
78
144
 
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ // The application.
3
+ //
4
+ // Everything this file does to the machine -- create the database, bind a
5
+ // port, hold stdio open until it is killed -- is exactly what a library must
6
+ // not do on import, which is why it lives here and not in src/index.ts. The
7
+ // package's `bin` points at this file; the package's `main` points at the
8
+ // library, which starts nothing.
9
+ //
10
+ // The web UI runs by default, as it always has. DMCP_NO_HTTP turns it off, for
11
+ // a host that spawns this as an MCP subprocess and has no use for an admin
12
+ // page it cannot close.
13
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
14
+ import { closeDatabase } from "../db/connection.js";
15
+ import { initializeSchema } from "../db/schema.js";
16
+ import { startHttpServer } from "../http/server.js";
17
+ // The full assembly -- core plus the RPG layer -- now lives one layer up
18
+ // (design §8, issue #17). The application always served the full surface,
19
+ // so it reaches for it here rather than the core-only `createCoreMcpServer`
20
+ // in ../mcp-server.js.
21
+ import { createMcpServer } from "../rpg/index.js";
22
+ import { httpPortFromEnv, setHttpPort, webUiEnabled } from "../utils/webui.js";
23
+ import { createLogger } from "../utils/logger.js";
24
+ const log = createLogger("bin");
25
+ async function main() {
26
+ // An application owns its database, so this is where the schema is brought
27
+ // up -- not at import time, and not in any module a consumer might load.
28
+ initializeSchema();
29
+ const server = createMcpServer();
30
+ if (webUiEnabled(process.env)) {
31
+ const actualPort = await startHttpServer(httpPortFromEnv(process.env));
32
+ setHttpPort(actualPort);
33
+ }
34
+ else {
35
+ log.info("Web UI disabled by DMCP_NO_HTTP; no port will be bound");
36
+ }
37
+ // Start MCP server with stdio transport
38
+ const transport = new StdioServerTransport();
39
+ await server.connect(transport);
40
+ }
41
+ // Handle cleanup
42
+ process.on("SIGINT", () => {
43
+ closeDatabase();
44
+ process.exit(0);
45
+ });
46
+ process.on("SIGTERM", () => {
47
+ closeDatabase();
48
+ process.exit(0);
49
+ });
50
+ main().catch((error) => {
51
+ log.error("Server error", { error: error instanceof Error ? error.message : String(error) });
52
+ console.error(error);
53
+ closeDatabase();
54
+ process.exit(1);
55
+ });
@@ -1,4 +1,36 @@
1
1
  import Database from "better-sqlite3";
2
+ export interface DataPathInputs {
3
+ env: NodeJS.ProcessEnv;
4
+ cwd: string;
5
+ home: string;
6
+ exists: (path: string) => boolean;
7
+ }
8
+ /**
9
+ * Resolve database path with the following priority:
10
+ * 1. `env.DMCP_DB_PATH` -- the explicit path, wins over everything. Passed
11
+ * through verbatim (including the literal ":memory:"), with
12
+ * `dataDir = dirname(dbPath)`.
13
+ * 2. The XDG data directory -- `env.XDG_DATA_HOME || join(home, ".local",
14
+ * "share")`, plus "/dmcp/games.db" -- but only when that directory
15
+ * already exists. An existing `~/.local/share/dmcp` means a previous
16
+ * install's data already lives there, and moving the default out from
17
+ * under it would orphan someone's saved state.
18
+ * 3. Otherwise `join(cwd, "data", "games.db")` -- the CONSUMING
19
+ * application's working directory. This is the fix for the "installed as
20
+ * a dependency" bug: the default lands where the application runs, never
21
+ * where the package is installed. `cwd` and `home` are supplied by the
22
+ * caller (see `resolveDataPath` below) and the package's own install
23
+ * location is never an input to this function, so it cannot resolve
24
+ * there -- structurally, not by a runtime check.
25
+ *
26
+ * Pure and deterministically testable: no ambient reads (`process.env`,
27
+ * `process.cwd()`, `os.homedir()`) and no filesystem access except through
28
+ * the injected `exists`. See `src/db/__tests__/dataPath.test.ts`.
29
+ */
30
+ export declare function resolveDataPathFrom(inputs: DataPathInputs): {
31
+ dataDir: string;
32
+ dbPath: string;
33
+ };
2
34
  export declare function getDatabase(): Database.Database;
3
35
  export declare function closeDatabase(): void;
4
36
  /**
@@ -1,41 +1,63 @@
1
1
  import Database from "better-sqlite3";
2
- import { fileURLToPath } from "url";
3
2
  import { dirname, join } from "path";
4
3
  import { mkdirSync, existsSync } from "fs";
5
4
  import { homedir } from "os";
6
5
  import { createLogger } from "../utils/logger.js";
7
6
  const log = createLogger("db");
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = dirname(__filename);
10
7
  /**
11
8
  * Resolve database path with the following priority:
12
- * 1. DMCP_DB_PATH environment variable (absolute path)
13
- * 2. XDG_DATA_HOME/dmcp/games.db (Linux/macOS standard)
14
- * 3. Fallback to ./data/games.db (relative to project)
9
+ * 1. `env.DMCP_DB_PATH` -- the explicit path, wins over everything. Passed
10
+ * through verbatim (including the literal ":memory:"), with
11
+ * `dataDir = dirname(dbPath)`.
12
+ * 2. The XDG data directory -- `env.XDG_DATA_HOME || join(home, ".local",
13
+ * "share")`, plus "/dmcp/games.db" -- but only when that directory
14
+ * already exists. An existing `~/.local/share/dmcp` means a previous
15
+ * install's data already lives there, and moving the default out from
16
+ * under it would orphan someone's saved state.
17
+ * 3. Otherwise `join(cwd, "data", "games.db")` -- the CONSUMING
18
+ * application's working directory. This is the fix for the "installed as
19
+ * a dependency" bug: the default lands where the application runs, never
20
+ * where the package is installed. `cwd` and `home` are supplied by the
21
+ * caller (see `resolveDataPath` below) and the package's own install
22
+ * location is never an input to this function, so it cannot resolve
23
+ * there -- structurally, not by a runtime check.
24
+ *
25
+ * Pure and deterministically testable: no ambient reads (`process.env`,
26
+ * `process.cwd()`, `os.homedir()`) and no filesystem access except through
27
+ * the injected `exists`. See `src/db/__tests__/dataPath.test.ts`.
15
28
  */
16
- function resolveDataPath() {
29
+ export function resolveDataPathFrom(inputs) {
30
+ const { env, cwd, home, exists } = inputs;
17
31
  // Priority 1: Explicit environment variable
18
- if (process.env.DMCP_DB_PATH) {
19
- const dbPath = process.env.DMCP_DB_PATH;
32
+ if (env.DMCP_DB_PATH) {
33
+ const dbPath = env.DMCP_DB_PATH;
20
34
  const dataDir = dirname(dbPath);
21
35
  log.info("Using database path from DMCP_DB_PATH", { dbPath });
22
36
  return { dataDir, dbPath };
23
37
  }
24
- // Priority 2: XDG Base Directory spec
25
- const xdgDataHome = process.env.XDG_DATA_HOME || join(homedir(), ".local", "share");
38
+ // Priority 2: XDG Base Directory spec, only if it already exists
39
+ const xdgDataHome = env.XDG_DATA_HOME || join(home, ".local", "share");
26
40
  const xdgDataDir = join(xdgDataHome, "dmcp");
27
41
  const xdgDbPath = join(xdgDataDir, "games.db");
28
- // Use XDG path if it exists or if we're not in a development context
29
- if (existsSync(xdgDataDir) || !existsSync(join(__dirname, "..", "..", "package.json"))) {
42
+ if (exists(xdgDataDir)) {
30
43
  log.info("Using XDG data directory", { dbPath: xdgDbPath });
31
44
  return { dataDir: xdgDataDir, dbPath: xdgDbPath };
32
45
  }
33
- // Priority 3: Fallback to project-relative path (development)
34
- const fallbackDataDir = join(__dirname, "..", "..", "data");
46
+ // Priority 3: Fallback to the consuming application's working directory
47
+ const fallbackDataDir = join(cwd, "data");
35
48
  const fallbackDbPath = join(fallbackDataDir, "games.db");
36
- log.debug("Using project-relative data directory", { dbPath: fallbackDbPath });
49
+ log.debug("Using cwd-relative data directory", { dbPath: fallbackDbPath });
37
50
  return { dataDir: fallbackDataDir, dbPath: fallbackDbPath };
38
51
  }
52
+ /** Thin real-world wrapper around `resolveDataPathFrom`. */
53
+ function resolveDataPath() {
54
+ return resolveDataPathFrom({
55
+ env: process.env,
56
+ cwd: process.cwd(),
57
+ home: homedir(),
58
+ exists: existsSync,
59
+ });
60
+ }
39
61
  // Resolved lazily (on first getDatabase() call after startup, or after every
40
62
  // closeDatabase()) rather than once at module-import time. This makes
41
63
  // DMCP_DB_PATH re-readable at any point before the first real connection is
@@ -1 +1,29 @@
1
- export declare function initializeSchema(): void;
1
+ import type Database from "better-sqlite3";
2
+ /**
3
+ * A schema change a consuming application wants applied to the same SQLite
4
+ * file the engine's own tables live in, during the engine's own startup
5
+ * pass. Passed as an array to `initializeSchema()`, not registered through
6
+ * a global function: a registry would make the engine's schema depend on
7
+ * module import order and on side effects at import time, which is exactly
8
+ * what this release is removing elsewhere. A parameter can't be "registered
9
+ * too late" -- it's either in the array passed to the one call that matters,
10
+ * or it isn't.
11
+ *
12
+ * There is no framework: no version table, no record of what already ran,
13
+ * no down-migrations. Every migration in the array runs on every startup,
14
+ * exactly like the engine's own DDL below, which is what forces `up` to be
15
+ * idempotent (`CREATE TABLE IF NOT EXISTS`, and
16
+ * `try { db.exec("ALTER TABLE ...") } catch {}` for added columns).
17
+ *
18
+ * Migrations run after every core table has been created, so `up` may
19
+ * declare a foreign key into one (e.g. `games`).
20
+ */
21
+ export interface SchemaMigration {
22
+ /** Stable identifier. Used for duplicate detection and error messages. */
23
+ name: string;
24
+ /** Applies the migration. MUST be idempotent: it runs on every startup. */
25
+ up(db: Database.Database): void;
26
+ }
27
+ export declare function initializeSchema(options?: {
28
+ migrations?: readonly SchemaMigration[];
29
+ }): void;