@react-text-game/core 0.2.2 → 0.3.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.
- package/README.md +2 -0
- package/dist/game.d.ts.map +1 -1
- package/dist/game.js +13 -0
- package/dist/game.js.map +1 -1
- package/dist/saves/hooks/useLoadGame.d.ts +3 -0
- package/dist/saves/hooks/useLoadGame.d.ts.map +1 -1
- package/dist/saves/hooks/useLoadGame.js +23 -1
- package/dist/saves/hooks/useLoadGame.js.map +1 -1
- package/dist/saves/index.d.ts +1 -0
- package/dist/saves/index.d.ts.map +1 -1
- package/dist/saves/index.js +1 -0
- package/dist/saves/index.js.map +1 -1
- package/dist/saves/migrations/EXAMPLE.d.ts +113 -0
- package/dist/saves/migrations/EXAMPLE.d.ts.map +1 -0
- package/dist/saves/migrations/EXAMPLE.js +304 -0
- package/dist/saves/migrations/EXAMPLE.js.map +1 -0
- package/dist/saves/migrations/index.d.ts +41 -0
- package/dist/saves/migrations/index.d.ts.map +1 -0
- package/dist/saves/migrations/index.js +40 -0
- package/dist/saves/migrations/index.js.map +1 -0
- package/dist/saves/migrations/registry.d.ts +72 -0
- package/dist/saves/migrations/registry.d.ts.map +1 -0
- package/dist/saves/migrations/registry.js +174 -0
- package/dist/saves/migrations/registry.js.map +1 -0
- package/dist/saves/migrations/runner.d.ts +45 -0
- package/dist/saves/migrations/runner.d.ts.map +1 -0
- package/dist/saves/migrations/runner.js +139 -0
- package/dist/saves/migrations/runner.js.map +1 -0
- package/dist/saves/migrations/types.d.ts +182 -0
- package/dist/saves/migrations/types.d.ts.map +1 -0
- package/dist/saves/migrations/types.js +2 -0
- package/dist/saves/migrations/types.js.map +1 -0
- package/dist/tests/migrations.test.d.ts +2 -0
- package/dist/tests/migrations.test.d.ts.map +1 -0
- package/dist/tests/migrations.test.js +602 -0
- package/dist/tests/migrations.test.js.map +1 -0
- package/package.json +2 -3
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Save Migration System
|
|
3
|
+
*
|
|
4
|
+
* This module provides a complete solution for versioning and migrating game saves.
|
|
5
|
+
* It allows developers to register migration functions that transform save data
|
|
6
|
+
* from older versions to newer versions.
|
|
7
|
+
*
|
|
8
|
+
* ## Key Concepts
|
|
9
|
+
*
|
|
10
|
+
* - **Migration**: A function that transforms save data from version A to version B
|
|
11
|
+
* - **Migration Path**: A sequence of migrations needed to go from an old version to the current version
|
|
12
|
+
* - **Migration Chain**: Migrations are applied sequentially (1.0.0 → 1.1.0 → 1.2.0 → 2.0.0)
|
|
13
|
+
*
|
|
14
|
+
* ## Usage
|
|
15
|
+
*
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { registerMigration } from '@react-text-game/core/saves';
|
|
18
|
+
*
|
|
19
|
+
* // Register a migration when you make a breaking change
|
|
20
|
+
* registerMigration({
|
|
21
|
+
* from: "1.0.0",
|
|
22
|
+
* to: "1.1.0",
|
|
23
|
+
* description: "Added player inventory system",
|
|
24
|
+
* migrate: (data) => ({
|
|
25
|
+
* ...data,
|
|
26
|
+
* player: {
|
|
27
|
+
* ...data.player,
|
|
28
|
+
* inventory: [] // Add default value
|
|
29
|
+
* }
|
|
30
|
+
* })
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* Migrations are automatically applied when loading saves via `useLoadGame()`.
|
|
35
|
+
*
|
|
36
|
+
* @module saves/migrations
|
|
37
|
+
*/
|
|
38
|
+
export { clearMigrations, findMigrationPath, getAllMigrations, registerMigration, validateMigrations, } from "./registry";
|
|
39
|
+
export { migrateToCurrentVersion, runMigrations } from "./runner";
|
|
40
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/saves/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,EACH,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,GACrB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { SaveMigration } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Registers a migration function for moving from one version to another.
|
|
4
|
+
*
|
|
5
|
+
* Migrations should be registered during game initialization, typically in
|
|
6
|
+
* your game's entry point after calling `Game.init()`.
|
|
7
|
+
*
|
|
8
|
+
* @param migration - The migration definition
|
|
9
|
+
* @throws Error if a migration with the same from->to path already exists
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* registerMigration({
|
|
14
|
+
* from: "1.0.0",
|
|
15
|
+
* to: "1.1.0",
|
|
16
|
+
* description: "Added player inventory",
|
|
17
|
+
* migrate: (data) => ({
|
|
18
|
+
* ...data,
|
|
19
|
+
* player: { ...data.player, inventory: [] }
|
|
20
|
+
* })
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function registerMigration(migration: SaveMigration): void;
|
|
25
|
+
/**
|
|
26
|
+
* Clears all registered migrations.
|
|
27
|
+
* Primarily used for testing.
|
|
28
|
+
*
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
export declare function clearMigrations(): void;
|
|
32
|
+
/**
|
|
33
|
+
* Gets all registered migrations.
|
|
34
|
+
*
|
|
35
|
+
* @returns Array of all registered migration definitions
|
|
36
|
+
*/
|
|
37
|
+
export declare function getAllMigrations(): SaveMigration[];
|
|
38
|
+
/**
|
|
39
|
+
* Finds the shortest migration path from one version to another using BFS.
|
|
40
|
+
*
|
|
41
|
+
* This function builds a graph of all registered migrations and uses
|
|
42
|
+
* breadth-first search to find the shortest sequence of migrations
|
|
43
|
+
* needed to go from the source version to the target version.
|
|
44
|
+
*
|
|
45
|
+
* @param fromVersion - The starting version (e.g., "1.0.0")
|
|
46
|
+
* @param toVersion - The target version (e.g., "2.0.0")
|
|
47
|
+
* @returns Array of migrations to apply in order, or null if no path exists
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* // Registered migrations: 1.0.0→1.1.0, 1.1.0→1.2.0, 1.2.0→2.0.0
|
|
52
|
+
* const path = findMigrationPath("1.0.0", "2.0.0");
|
|
53
|
+
* // Returns: [migration_1_0_to_1_1, migration_1_1_to_1_2, migration_1_2_to_2_0]
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare function findMigrationPath(fromVersion: string, toVersion: string): SaveMigration[] | null;
|
|
57
|
+
/**
|
|
58
|
+
* Validates that the migration registry forms a valid chain.
|
|
59
|
+
*
|
|
60
|
+
* Checks for:
|
|
61
|
+
* - Orphaned migrations (from versions with no incoming migrations)
|
|
62
|
+
* - Dead ends (to versions with no outgoing migrations, except latest)
|
|
63
|
+
* - Duplicate migrations
|
|
64
|
+
*
|
|
65
|
+
* @param latestVersion - The current/latest version of the game
|
|
66
|
+
* @returns Validation result with any issues found
|
|
67
|
+
*/
|
|
68
|
+
export declare function validateMigrations(latestVersion: string): {
|
|
69
|
+
valid: boolean;
|
|
70
|
+
issues: string[];
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/saves/migrations/registry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAaxC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI,CAahE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,aAAa,EAAE,CAElD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAC7B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAClB,aAAa,EAAE,GAAG,IAAI,CA+DxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG;IACvD,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CACpB,CA6CA"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { logger } from "../../logger";
|
|
2
|
+
/**
|
|
3
|
+
* In-memory registry of all registered migrations.
|
|
4
|
+
* Maps "from -> to" version pairs to migration objects.
|
|
5
|
+
*/
|
|
6
|
+
const migrations = new Map();
|
|
7
|
+
/**
|
|
8
|
+
* Helper to create a unique key for a migration edge
|
|
9
|
+
*/
|
|
10
|
+
const getMigrationKey = (from, to) => `${from}→${to}`;
|
|
11
|
+
/**
|
|
12
|
+
* Registers a migration function for moving from one version to another.
|
|
13
|
+
*
|
|
14
|
+
* Migrations should be registered during game initialization, typically in
|
|
15
|
+
* your game's entry point after calling `Game.init()`.
|
|
16
|
+
*
|
|
17
|
+
* @param migration - The migration definition
|
|
18
|
+
* @throws Error if a migration with the same from->to path already exists
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* registerMigration({
|
|
23
|
+
* from: "1.0.0",
|
|
24
|
+
* to: "1.1.0",
|
|
25
|
+
* description: "Added player inventory",
|
|
26
|
+
* migrate: (data) => ({
|
|
27
|
+
* ...data,
|
|
28
|
+
* player: { ...data.player, inventory: [] }
|
|
29
|
+
* })
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function registerMigration(migration) {
|
|
34
|
+
const key = getMigrationKey(migration.from, migration.to);
|
|
35
|
+
if (migrations.has(key)) {
|
|
36
|
+
throw new Error(`Migration from ${migration.from} to ${migration.to} is already registered`);
|
|
37
|
+
}
|
|
38
|
+
migrations.set(key, migration);
|
|
39
|
+
logger.log(`Registered save migration: ${migration.from} → ${migration.to} (${migration.description})`);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Clears all registered migrations.
|
|
43
|
+
* Primarily used for testing.
|
|
44
|
+
*
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
export function clearMigrations() {
|
|
48
|
+
migrations.clear();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Gets all registered migrations.
|
|
52
|
+
*
|
|
53
|
+
* @returns Array of all registered migration definitions
|
|
54
|
+
*/
|
|
55
|
+
export function getAllMigrations() {
|
|
56
|
+
return Array.from(migrations.values());
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Finds the shortest migration path from one version to another using BFS.
|
|
60
|
+
*
|
|
61
|
+
* This function builds a graph of all registered migrations and uses
|
|
62
|
+
* breadth-first search to find the shortest sequence of migrations
|
|
63
|
+
* needed to go from the source version to the target version.
|
|
64
|
+
*
|
|
65
|
+
* @param fromVersion - The starting version (e.g., "1.0.0")
|
|
66
|
+
* @param toVersion - The target version (e.g., "2.0.0")
|
|
67
|
+
* @returns Array of migrations to apply in order, or null if no path exists
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* // Registered migrations: 1.0.0→1.1.0, 1.1.0→1.2.0, 1.2.0→2.0.0
|
|
72
|
+
* const path = findMigrationPath("1.0.0", "2.0.0");
|
|
73
|
+
* // Returns: [migration_1_0_to_1_1, migration_1_1_to_1_2, migration_1_2_to_2_0]
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export function findMigrationPath(fromVersion, toVersion) {
|
|
77
|
+
// If versions are the same, no migration needed
|
|
78
|
+
if (fromVersion === toVersion) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
// Build adjacency list of version graph
|
|
82
|
+
const graph = new Map();
|
|
83
|
+
const migrationMap = new Map();
|
|
84
|
+
for (const migration of migrations.values()) {
|
|
85
|
+
if (!graph.has(migration.from)) {
|
|
86
|
+
graph.set(migration.from, []);
|
|
87
|
+
}
|
|
88
|
+
graph.get(migration.from).push(migration.to);
|
|
89
|
+
migrationMap.set(getMigrationKey(migration.from, migration.to), migration);
|
|
90
|
+
}
|
|
91
|
+
// BFS to find shortest path
|
|
92
|
+
const queue = [
|
|
93
|
+
{ version: fromVersion, path: [] },
|
|
94
|
+
];
|
|
95
|
+
const visited = new Set([fromVersion]);
|
|
96
|
+
while (queue.length > 0) {
|
|
97
|
+
const current = queue.shift();
|
|
98
|
+
// Check if we reached the target
|
|
99
|
+
if (current.version === toVersion) {
|
|
100
|
+
// Reconstruct the migration path
|
|
101
|
+
const migrationPath = [];
|
|
102
|
+
for (let i = 0; i < current.path.length; i++) {
|
|
103
|
+
const from = i === 0 ? fromVersion : current.path[i - 1];
|
|
104
|
+
const to = current.path[i];
|
|
105
|
+
if (!from || !to) {
|
|
106
|
+
continue; // Skip if either is undefined
|
|
107
|
+
}
|
|
108
|
+
const migration = migrationMap.get(getMigrationKey(from, to));
|
|
109
|
+
if (migration) {
|
|
110
|
+
migrationPath.push(migration);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return migrationPath;
|
|
114
|
+
}
|
|
115
|
+
// Explore neighbors
|
|
116
|
+
const neighbors = graph.get(current.version) || [];
|
|
117
|
+
for (const neighbor of neighbors) {
|
|
118
|
+
if (!visited.has(neighbor)) {
|
|
119
|
+
visited.add(neighbor);
|
|
120
|
+
queue.push({
|
|
121
|
+
version: neighbor,
|
|
122
|
+
path: [...current.path, neighbor],
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// No path found
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Validates that the migration registry forms a valid chain.
|
|
132
|
+
*
|
|
133
|
+
* Checks for:
|
|
134
|
+
* - Orphaned migrations (from versions with no incoming migrations)
|
|
135
|
+
* - Dead ends (to versions with no outgoing migrations, except latest)
|
|
136
|
+
* - Duplicate migrations
|
|
137
|
+
*
|
|
138
|
+
* @param latestVersion - The current/latest version of the game
|
|
139
|
+
* @returns Validation result with any issues found
|
|
140
|
+
*/
|
|
141
|
+
export function validateMigrations(latestVersion) {
|
|
142
|
+
const issues = [];
|
|
143
|
+
const allMigrations = getAllMigrations();
|
|
144
|
+
if (allMigrations.length === 0) {
|
|
145
|
+
return { valid: true, issues: [] };
|
|
146
|
+
}
|
|
147
|
+
// Collect all version nodes
|
|
148
|
+
const fromVersions = new Set();
|
|
149
|
+
const toVersions = new Set();
|
|
150
|
+
for (const migration of allMigrations) {
|
|
151
|
+
fromVersions.add(migration.from);
|
|
152
|
+
toVersions.add(migration.to);
|
|
153
|
+
}
|
|
154
|
+
// Find orphaned versions (versions that are targets but have no path from any base version)
|
|
155
|
+
const baseVersions = new Set(Array.from(fromVersions).filter((v) => !toVersions.has(v)));
|
|
156
|
+
// Check if all versions can reach the latest version
|
|
157
|
+
for (const baseVersion of baseVersions) {
|
|
158
|
+
const path = findMigrationPath(baseVersion, latestVersion);
|
|
159
|
+
if (path === null) {
|
|
160
|
+
issues.push(`No migration path from base version ${baseVersion} to latest version ${latestVersion}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Check for dead ends (versions that have no outgoing migrations, except latest)
|
|
164
|
+
for (const toVersion of toVersions) {
|
|
165
|
+
if (toVersion !== latestVersion && !fromVersions.has(toVersion)) {
|
|
166
|
+
issues.push(`Dead end detected: Version ${toVersion} has no outgoing migrations`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
valid: issues.length === 0,
|
|
171
|
+
issues,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/saves/migrations/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { GameSaveState } from "../../types";
|
|
2
|
+
import { MigrationOptions, MigrationResult } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Runs the migration chain to migrate save data from one version to another.
|
|
5
|
+
*
|
|
6
|
+
* This function:
|
|
7
|
+
* 1. Finds the shortest migration path using BFS
|
|
8
|
+
* 2. Applies each migration in sequence
|
|
9
|
+
* 3. Returns the migrated data or error information
|
|
10
|
+
*
|
|
11
|
+
* @param data - The save data to migrate
|
|
12
|
+
* @param fromVersion - The version the save was created with
|
|
13
|
+
* @param toVersion - The target version to migrate to (usually current game version)
|
|
14
|
+
* @param options - Migration behavior options
|
|
15
|
+
* @returns Migration result with success status and migrated data
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const result = runMigrations(oldSaveData, "1.0.0", "2.0.0");
|
|
20
|
+
* if (result.success) {
|
|
21
|
+
* console.log("Migrated data:", result.data);
|
|
22
|
+
* } else {
|
|
23
|
+
* console.error("Migration failed:", result.error);
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function runMigrations(data: GameSaveState, fromVersion: string, toVersion: string, options?: MigrationOptions): MigrationResult;
|
|
28
|
+
/**
|
|
29
|
+
* Convenience function to migrate save data to the current game version.
|
|
30
|
+
*
|
|
31
|
+
* @param data - The save data to migrate
|
|
32
|
+
* @param fromVersion - The version the save was created with
|
|
33
|
+
* @param options - Migration behavior options
|
|
34
|
+
* @returns Migration result
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* const result = migrateToCurrentVersion(saveData, "1.0.0");
|
|
39
|
+
* if (result.success) {
|
|
40
|
+
* Game.setState(result.data);
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare function migrateToCurrentVersion(data: GameSaveState, fromVersion: string, options?: MigrationOptions): MigrationResult;
|
|
45
|
+
//# sourceMappingURL=runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/saves/migrations/runner.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAGvC,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,aAAa,CACzB,IAAI,EAAE,aAAa,EACnB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,gBAAqB,GAC/B,eAAe,CAqIjB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CACnC,IAAI,EAAE,aAAa,EACnB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,gBAAgB,GAC3B,eAAe,CAGjB"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { logger } from "../../logger";
|
|
2
|
+
import { _getOptions } from "../../options";
|
|
3
|
+
import { findMigrationPath } from "./registry";
|
|
4
|
+
/**
|
|
5
|
+
* Runs the migration chain to migrate save data from one version to another.
|
|
6
|
+
*
|
|
7
|
+
* This function:
|
|
8
|
+
* 1. Finds the shortest migration path using BFS
|
|
9
|
+
* 2. Applies each migration in sequence
|
|
10
|
+
* 3. Returns the migrated data or error information
|
|
11
|
+
*
|
|
12
|
+
* @param data - The save data to migrate
|
|
13
|
+
* @param fromVersion - The version the save was created with
|
|
14
|
+
* @param toVersion - The target version to migrate to (usually current game version)
|
|
15
|
+
* @param options - Migration behavior options
|
|
16
|
+
* @returns Migration result with success status and migrated data
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const result = runMigrations(oldSaveData, "1.0.0", "2.0.0");
|
|
21
|
+
* if (result.success) {
|
|
22
|
+
* console.log("Migrated data:", result.data);
|
|
23
|
+
* } else {
|
|
24
|
+
* console.error("Migration failed:", result.error);
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function runMigrations(data, fromVersion, toVersion, options = {}) {
|
|
29
|
+
const { strict = false, verbose } = options;
|
|
30
|
+
// Determine verbosity (default to dev mode setting)
|
|
31
|
+
const shouldLog = verbose !== undefined ? verbose : _getOptions().isDevMode;
|
|
32
|
+
// If versions match, no migration needed
|
|
33
|
+
if (fromVersion === toVersion) {
|
|
34
|
+
if (shouldLog) {
|
|
35
|
+
logger.log(`No migration needed: save version ${fromVersion} matches current version`);
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
success: true,
|
|
39
|
+
data,
|
|
40
|
+
migrationsApplied: [],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// Find migration path
|
|
44
|
+
if (shouldLog) {
|
|
45
|
+
logger.log(`Finding migration path from ${fromVersion} to ${toVersion}...`);
|
|
46
|
+
}
|
|
47
|
+
const migrationPath = findMigrationPath(fromVersion, toVersion);
|
|
48
|
+
if (migrationPath === null) {
|
|
49
|
+
const errorMsg = `No migration path found from version ${fromVersion} to ${toVersion}`;
|
|
50
|
+
if (strict) {
|
|
51
|
+
throw new Error(errorMsg);
|
|
52
|
+
}
|
|
53
|
+
logger.warn(`${errorMsg}. Returning original data. This may cause compatibility issues.`);
|
|
54
|
+
return {
|
|
55
|
+
success: false,
|
|
56
|
+
error: errorMsg,
|
|
57
|
+
migrationsApplied: [],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (migrationPath.length === 0) {
|
|
61
|
+
return {
|
|
62
|
+
success: true,
|
|
63
|
+
data,
|
|
64
|
+
migrationsApplied: [],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// Log migration plan
|
|
68
|
+
if (shouldLog) {
|
|
69
|
+
logger.log(`Found migration path with ${migrationPath.length} step(s):`);
|
|
70
|
+
migrationPath.forEach((m, i) => {
|
|
71
|
+
logger.log(` ${i + 1}. ${m.from} → ${m.to}: ${m.description}`);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// Apply migrations sequentially
|
|
75
|
+
let currentData = data;
|
|
76
|
+
const appliedMigrations = [];
|
|
77
|
+
try {
|
|
78
|
+
for (const migration of migrationPath) {
|
|
79
|
+
if (shouldLog) {
|
|
80
|
+
logger.log(`Applying migration: ${migration.from} → ${migration.to}`);
|
|
81
|
+
}
|
|
82
|
+
// Run migration
|
|
83
|
+
const migratedData = migration.migrate(currentData);
|
|
84
|
+
// Validate migration output
|
|
85
|
+
if (!migratedData ||
|
|
86
|
+
typeof migratedData !== "object" ||
|
|
87
|
+
Array.isArray(migratedData)) {
|
|
88
|
+
throw new Error(`Migration ${migration.from} → ${migration.to} returned invalid data type. Expected object, got ${typeof migratedData}`);
|
|
89
|
+
}
|
|
90
|
+
currentData = migratedData;
|
|
91
|
+
appliedMigrations.push({
|
|
92
|
+
from: migration.from,
|
|
93
|
+
to: migration.to,
|
|
94
|
+
description: migration.description,
|
|
95
|
+
});
|
|
96
|
+
if (shouldLog) {
|
|
97
|
+
logger.log(`Successfully applied migration: ${migration.from} → ${migration.to}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (shouldLog) {
|
|
101
|
+
logger.log(`Migration complete: ${fromVersion} → ${toVersion} (${appliedMigrations.length} steps)`);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
success: true,
|
|
105
|
+
data: currentData,
|
|
106
|
+
migrationsApplied: appliedMigrations,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
111
|
+
logger.error(`Migration failed at step ${appliedMigrations.length + 1}: ${errorMsg}`);
|
|
112
|
+
return {
|
|
113
|
+
success: false,
|
|
114
|
+
error: errorMsg,
|
|
115
|
+
migrationsApplied: appliedMigrations,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Convenience function to migrate save data to the current game version.
|
|
121
|
+
*
|
|
122
|
+
* @param data - The save data to migrate
|
|
123
|
+
* @param fromVersion - The version the save was created with
|
|
124
|
+
* @param options - Migration behavior options
|
|
125
|
+
* @returns Migration result
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* const result = migrateToCurrentVersion(saveData, "1.0.0");
|
|
130
|
+
* if (result.success) {
|
|
131
|
+
* Game.setState(result.data);
|
|
132
|
+
* }
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export function migrateToCurrentVersion(data, fromVersion, options) {
|
|
136
|
+
const currentVersion = _getOptions().gameVersion;
|
|
137
|
+
return runMigrations(data, fromVersion, currentVersion, options);
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../src/saves/migrations/runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { GameSaveState } from "../../types";
|
|
2
|
+
export type MigrationGameSaveState = Partial<GameSaveState> & Record<string, unknown>;
|
|
3
|
+
/**
|
|
4
|
+
* A function that migrates game save data from one version to another.
|
|
5
|
+
*
|
|
6
|
+
* Migration functions should be pure (no side effects) and should always
|
|
7
|
+
* return a new object rather than mutating the input.
|
|
8
|
+
*
|
|
9
|
+
* @template T - The shape of the game save state for this migration. Can be a partial
|
|
10
|
+
* type if the migration only touches specific fields. Defaults to GameSaveState.
|
|
11
|
+
*
|
|
12
|
+
* @param data - The game save state at the source version
|
|
13
|
+
* @returns The migrated game save state at the target version
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* Basic migration without specific types:
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const migrateFn: SaveMigrationFn = (data) => {
|
|
19
|
+
* return {
|
|
20
|
+
* ...data,
|
|
21
|
+
* player: {
|
|
22
|
+
* ...data.player,
|
|
23
|
+
* health: data.player.hp ?? 100, // Rename hp to health
|
|
24
|
+
* }
|
|
25
|
+
* };
|
|
26
|
+
* };
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* Type-safe migration with specific field types:
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const migrateFn: SaveMigrationFn<{ player?: { inventory: string[] } }> = (data) => {
|
|
33
|
+
* const player = data.player || {};
|
|
34
|
+
* return {
|
|
35
|
+
* ...data,
|
|
36
|
+
* player: {
|
|
37
|
+
* ...player,
|
|
38
|
+
* inventory: [], // TypeScript knows this should be string[]
|
|
39
|
+
* }
|
|
40
|
+
* };
|
|
41
|
+
* };
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export type SaveMigrationFn<T extends MigrationGameSaveState = GameSaveState> = (data: T) => T;
|
|
45
|
+
/**
|
|
46
|
+
* Defines a migration from one game version to another.
|
|
47
|
+
*
|
|
48
|
+
* Migrations are registered by developers when they make breaking changes
|
|
49
|
+
* to the game save structure. The migration system will automatically
|
|
50
|
+
* chain migrations together to migrate saves from any old version to the
|
|
51
|
+
* current version.
|
|
52
|
+
*
|
|
53
|
+
* @template T - The shape of the game save state for this migration. Can be a partial
|
|
54
|
+
* type if the migration only touches specific fields. Defaults to GameSaveState.
|
|
55
|
+
* Using specific types provides better type safety and IDE autocomplete.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* Basic migration without specific types:
|
|
59
|
+
* ```typescript
|
|
60
|
+
* const migration: SaveMigration = {
|
|
61
|
+
* from: "1.0.0",
|
|
62
|
+
* to: "1.1.0",
|
|
63
|
+
* description: "Added inventory system",
|
|
64
|
+
* migrate: (save) => ({ ...save, inventory: [] })
|
|
65
|
+
* };
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* Type-safe migration with specific field types:
|
|
70
|
+
* ```typescript
|
|
71
|
+
* const migration: SaveMigration<{ player?: { inventory: string[] } }> = {
|
|
72
|
+
* from: "1.0.0",
|
|
73
|
+
* to: "1.1.0",
|
|
74
|
+
* description: "Added inventory system",
|
|
75
|
+
* migrate: (save) => {
|
|
76
|
+
* const player = save.player || {};
|
|
77
|
+
* return {
|
|
78
|
+
* ...save,
|
|
79
|
+
* player: {
|
|
80
|
+
* ...player,
|
|
81
|
+
* inventory: [], // TypeScript validates the type
|
|
82
|
+
* }
|
|
83
|
+
* };
|
|
84
|
+
* }
|
|
85
|
+
* };
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export interface SaveMigration<T extends MigrationGameSaveState = GameSaveState> {
|
|
89
|
+
/**
|
|
90
|
+
* The source version this migration starts from.
|
|
91
|
+
* Should be a valid semver string (e.g., "1.0.0", "1.2.3")
|
|
92
|
+
*/
|
|
93
|
+
from: string;
|
|
94
|
+
/**
|
|
95
|
+
* The target version this migration migrates to.
|
|
96
|
+
* Should be a valid semver string (e.g., "1.1.0", "2.0.0")
|
|
97
|
+
*/
|
|
98
|
+
to: string;
|
|
99
|
+
/**
|
|
100
|
+
* Human-readable description of what this migration does.
|
|
101
|
+
* Used for logging and debugging.
|
|
102
|
+
*
|
|
103
|
+
* @example "Added player inventory system"
|
|
104
|
+
* @example "Renamed 'hp' field to 'health'"
|
|
105
|
+
*/
|
|
106
|
+
description: string;
|
|
107
|
+
/**
|
|
108
|
+
* The migration function that transforms the data.
|
|
109
|
+
* Should be pure and not mutate the input.
|
|
110
|
+
*/
|
|
111
|
+
migrate: SaveMigrationFn<T>;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Result of running a migration chain.
|
|
115
|
+
*
|
|
116
|
+
* @template T - The type of the migrated data. Should match the target version's
|
|
117
|
+
* save state structure. Defaults to GameSaveState.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```typescript
|
|
121
|
+
* const result: MigrationResult = runMigrations(oldSave, "1.0.0", "2.0.0");
|
|
122
|
+
* if (result.success) {
|
|
123
|
+
* console.log("Migrated data:", result.data);
|
|
124
|
+
* console.log("Applied migrations:", result.migrationsApplied);
|
|
125
|
+
* } else {
|
|
126
|
+
* console.error("Migration failed:", result.error);
|
|
127
|
+
* }
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* Type-safe migration result:
|
|
132
|
+
* ```typescript
|
|
133
|
+
* type NewSaveFormat = {
|
|
134
|
+
* player: { stats: { health: number; mana: number } }
|
|
135
|
+
* } & Record<string, unknown>;
|
|
136
|
+
*
|
|
137
|
+
* const result: MigrationResult<NewSaveFormat> = runMigrations(oldSave, "1.0.0", "2.0.0");
|
|
138
|
+
* if (result.success && result.data) {
|
|
139
|
+
* // TypeScript knows about the new structure
|
|
140
|
+
* console.log(result.data.player.stats.health);
|
|
141
|
+
* }
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
export interface MigrationResult<T extends GameSaveState = GameSaveState> {
|
|
145
|
+
/**
|
|
146
|
+
* Whether the migration was successful
|
|
147
|
+
*/
|
|
148
|
+
success: boolean;
|
|
149
|
+
/**
|
|
150
|
+
* The migrated data (if successful)
|
|
151
|
+
*/
|
|
152
|
+
data?: T;
|
|
153
|
+
/**
|
|
154
|
+
* Error message (if failed)
|
|
155
|
+
*/
|
|
156
|
+
error?: string;
|
|
157
|
+
/**
|
|
158
|
+
* List of migrations that were applied, in order
|
|
159
|
+
*/
|
|
160
|
+
migrationsApplied: Array<{
|
|
161
|
+
from: string;
|
|
162
|
+
to: string;
|
|
163
|
+
description: string;
|
|
164
|
+
}>;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Options for migration behavior
|
|
168
|
+
*/
|
|
169
|
+
export interface MigrationOptions {
|
|
170
|
+
/**
|
|
171
|
+
* Whether to throw an error if no migration path is found.
|
|
172
|
+
* If false, returns the original data unchanged.
|
|
173
|
+
* @default false
|
|
174
|
+
*/
|
|
175
|
+
strict?: boolean;
|
|
176
|
+
/**
|
|
177
|
+
* Whether to log migration steps for debugging.
|
|
178
|
+
* @default true in dev mode, false in production
|
|
179
|
+
*/
|
|
180
|
+
verbose?: boolean;
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=types.d.ts.map
|