@react-text-game/core 0.2.2 → 0.4.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 +100 -1
- package/dist/game.d.ts.map +1 -1
- package/dist/game.js +15 -0
- package/dist/game.js.map +1 -1
- package/dist/i18n/constants.d.ts +21 -0
- package/dist/i18n/constants.d.ts.map +1 -0
- package/dist/i18n/constants.js +21 -0
- package/dist/i18n/constants.js.map +1 -0
- package/dist/i18n/hooks/index.d.ts +13 -0
- package/dist/i18n/hooks/index.d.ts.map +1 -0
- package/dist/i18n/hooks/index.js +13 -0
- package/dist/i18n/hooks/index.js.map +1 -0
- package/dist/i18n/hooks/useGameTranslation.d.ts +81 -0
- package/dist/i18n/hooks/useGameTranslation.d.ts.map +1 -0
- package/dist/i18n/hooks/useGameTranslation.js +102 -0
- package/dist/i18n/hooks/useGameTranslation.js.map +1 -0
- package/dist/i18n/index.d.ts +69 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +69 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/init.d.ts +62 -0
- package/dist/i18n/init.d.ts.map +1 -0
- package/dist/i18n/init.js +114 -0
- package/dist/i18n/init.js.map +1 -0
- package/dist/i18n/types.d.ts +54 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n/types.js +2 -0
- package/dist/i18n/types.js.map +1 -0
- package/dist/i18n/utils.d.ts +55 -0
- package/dist/i18n/utils.d.ts.map +1 -0
- package/dist/i18n/utils.js +69 -0
- package/dist/i18n/utils.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/options.d.ts +2 -0
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +2 -0
- package/dist/options.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/game.test.js.map +1 -1
- package/dist/tests/i18n.test.d.ts +2 -0
- package/dist/tests/i18n.test.d.ts.map +1 -0
- package/dist/tests/i18n.test.js +590 -0
- package/dist/tests/i18n.test.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 +17 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EXAMPLE.d.ts","sourceRoot":"","sources":["../../../src/saves/migrations/EXAMPLE.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA+KH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,SAYpC;AAMD;;;GAGG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6CrB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,kBAgCvC;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG"}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EXAMPLE: Save Migration System Usage
|
|
3
|
+
*
|
|
4
|
+
* This file demonstrates how to use the save migration system in a real game.
|
|
5
|
+
* It shows a complete migration history from version 1.0.0 to 3.0.0.
|
|
6
|
+
*/
|
|
7
|
+
import { registerMigration } from "./index";
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// MIGRATION HISTORY
|
|
10
|
+
// ============================================================================
|
|
11
|
+
/**
|
|
12
|
+
* Version 1.0.0 → 1.1.0
|
|
13
|
+
* Added: Inventory system
|
|
14
|
+
*
|
|
15
|
+
* In version 1.1.0, we added an inventory system to the game.
|
|
16
|
+
* Players who saved in 1.0.0 don't have an inventory field,
|
|
17
|
+
* so we add an empty array as the default.
|
|
18
|
+
*
|
|
19
|
+
* NOTE: This migration uses the generic type parameter to specify
|
|
20
|
+
* the exact shape of the data it operates on. This provides:
|
|
21
|
+
* - Better type safety when accessing save.player.inventory
|
|
22
|
+
* - IDE autocomplete for the specific fields
|
|
23
|
+
* - Compile-time validation of the migration logic
|
|
24
|
+
*/
|
|
25
|
+
const migration_1_0_to_1_1 = {
|
|
26
|
+
from: "1.0.0",
|
|
27
|
+
to: "1.1.0",
|
|
28
|
+
description: "Added player inventory system",
|
|
29
|
+
migrate: (save) => {
|
|
30
|
+
const player = save.player || {};
|
|
31
|
+
return {
|
|
32
|
+
...save,
|
|
33
|
+
player: {
|
|
34
|
+
...player,
|
|
35
|
+
inventory: [],
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Version 1.1.0 → 1.2.0
|
|
42
|
+
* Added: Quest tracking system
|
|
43
|
+
*
|
|
44
|
+
* We added a quest system with active and completed quests.
|
|
45
|
+
*
|
|
46
|
+
* NOTE: This migration does NOT use the generic type parameter,
|
|
47
|
+
* relying on the default GameSaveState type. This is fine for simple
|
|
48
|
+
* migrations where type safety is less critical. Use generics when:
|
|
49
|
+
* - You need type safety for complex nested structures
|
|
50
|
+
* - You want IDE autocomplete for specific fields
|
|
51
|
+
* - The migration logic is non-trivial and could benefit from type checking
|
|
52
|
+
*/
|
|
53
|
+
const migration_1_1_to_1_2 = {
|
|
54
|
+
from: "1.1.0",
|
|
55
|
+
to: "1.2.0",
|
|
56
|
+
description: "Added quest tracking system",
|
|
57
|
+
migrate: (save) => ({
|
|
58
|
+
...save,
|
|
59
|
+
quests: {
|
|
60
|
+
active: [],
|
|
61
|
+
completed: [],
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Version 1.2.0 → 2.0.0 (MAJOR VERSION)
|
|
67
|
+
* Changed: Renamed 'hp' to 'health', 'mp' to 'mana'
|
|
68
|
+
* Changed: Moved stats into a separate 'stats' object
|
|
69
|
+
*
|
|
70
|
+
* This is a breaking change that restructures how player stats are stored.
|
|
71
|
+
*/
|
|
72
|
+
const migration_1_2_to_2_0 = {
|
|
73
|
+
from: "1.2.0",
|
|
74
|
+
to: "2.0.0",
|
|
75
|
+
description: "Restructured player stats: renamed hp→health, mp→mana, moved to stats object",
|
|
76
|
+
migrate: (save) => {
|
|
77
|
+
const player = save.player;
|
|
78
|
+
return {
|
|
79
|
+
...save,
|
|
80
|
+
player: {
|
|
81
|
+
name: player.name,
|
|
82
|
+
level: player.level ?? 1,
|
|
83
|
+
experience: player.experience ?? 0,
|
|
84
|
+
inventory: player.inventory ?? [],
|
|
85
|
+
stats: {
|
|
86
|
+
health: player.hp ?? 100,
|
|
87
|
+
mana: player.mp ?? 50,
|
|
88
|
+
maxHealth: player.maxHp ?? 100,
|
|
89
|
+
maxMana: player.maxMp ?? 50,
|
|
90
|
+
strength: player.strength ?? 10,
|
|
91
|
+
intelligence: player.intelligence ?? 10,
|
|
92
|
+
agility: player.agility ?? 10,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Version 2.0.0 → 2.1.0
|
|
100
|
+
* Added: Skills system
|
|
101
|
+
* Added: Player location tracking
|
|
102
|
+
*/
|
|
103
|
+
const migration_2_0_to_2_1 = {
|
|
104
|
+
from: "2.0.0",
|
|
105
|
+
to: "2.1.0",
|
|
106
|
+
description: "Added skills system and location tracking",
|
|
107
|
+
migrate: (save) => {
|
|
108
|
+
const player = (save.player || {});
|
|
109
|
+
return {
|
|
110
|
+
...save,
|
|
111
|
+
player: {
|
|
112
|
+
...player,
|
|
113
|
+
skills: {
|
|
114
|
+
combat: 1,
|
|
115
|
+
magic: 1,
|
|
116
|
+
crafting: 1,
|
|
117
|
+
},
|
|
118
|
+
location: {
|
|
119
|
+
passageId: "start",
|
|
120
|
+
zone: "village",
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Version 2.1.0 → 3.0.0 (MAJOR VERSION)
|
|
128
|
+
* Changed: Completely redesigned inventory system
|
|
129
|
+
* - Old: Array of item names
|
|
130
|
+
* - New: Array of item objects with id, quantity, metadata
|
|
131
|
+
*
|
|
132
|
+
* This migration converts the old simple inventory to the new detailed format.
|
|
133
|
+
*/
|
|
134
|
+
const migration_2_1_to_3_0 = {
|
|
135
|
+
from: "2.1.0",
|
|
136
|
+
to: "3.0.0",
|
|
137
|
+
description: "Redesigned inventory to use item objects instead of strings",
|
|
138
|
+
migrate: (save) => {
|
|
139
|
+
const player = save.player;
|
|
140
|
+
const oldInventory = (player.inventory || []);
|
|
141
|
+
// Count duplicate items
|
|
142
|
+
const itemCounts = new Map();
|
|
143
|
+
for (const itemName of oldInventory) {
|
|
144
|
+
itemCounts.set(itemName, (itemCounts.get(itemName) || 0) + 1);
|
|
145
|
+
}
|
|
146
|
+
// Convert to new format
|
|
147
|
+
const newInventory = Array.from(itemCounts.entries()).map(([name, quantity], index) => ({
|
|
148
|
+
id: `item_${index}`,
|
|
149
|
+
name,
|
|
150
|
+
quantity,
|
|
151
|
+
metadata: {},
|
|
152
|
+
}));
|
|
153
|
+
return {
|
|
154
|
+
...save,
|
|
155
|
+
player: {
|
|
156
|
+
...player,
|
|
157
|
+
inventory: newInventory,
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
// ============================================================================
|
|
163
|
+
// MIGRATION REGISTRATION
|
|
164
|
+
// ============================================================================
|
|
165
|
+
/**
|
|
166
|
+
* Call this function during game initialization to register all migrations.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```typescript
|
|
170
|
+
* import { Game } from '@react-text-game/core';
|
|
171
|
+
* import { registerAllMigrations } from './migrations';
|
|
172
|
+
*
|
|
173
|
+
* async function initGame() {
|
|
174
|
+
* await Game.init({ gameVersion: "3.0.0", ... });
|
|
175
|
+
* registerAllMigrations();
|
|
176
|
+
* }
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
export function registerAllMigrations() {
|
|
180
|
+
// Register in chronological order for clarity
|
|
181
|
+
// The order doesn't actually matter - the system uses BFS to find the path
|
|
182
|
+
registerMigration(migration_1_0_to_1_1);
|
|
183
|
+
registerMigration(migration_1_1_to_1_2);
|
|
184
|
+
registerMigration(migration_1_2_to_2_0);
|
|
185
|
+
registerMigration(migration_2_0_to_2_1);
|
|
186
|
+
registerMigration(migration_2_1_to_3_0);
|
|
187
|
+
console.log("✓ Registered 5 save migrations covering versions 1.0.0 → 3.0.0");
|
|
188
|
+
}
|
|
189
|
+
// ============================================================================
|
|
190
|
+
// TESTING MIGRATIONS
|
|
191
|
+
// ============================================================================
|
|
192
|
+
/**
|
|
193
|
+
* Example test data for each version.
|
|
194
|
+
* Use these to test your migrations.
|
|
195
|
+
*/
|
|
196
|
+
export const testSaves = {
|
|
197
|
+
"1.0.0": {
|
|
198
|
+
player: {
|
|
199
|
+
name: "Hero",
|
|
200
|
+
hp: 80,
|
|
201
|
+
mp: 30,
|
|
202
|
+
maxHp: 100,
|
|
203
|
+
maxMp: 50,
|
|
204
|
+
level: 5,
|
|
205
|
+
experience: 1200,
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
"1.1.0": {
|
|
209
|
+
player: {
|
|
210
|
+
name: "Hero",
|
|
211
|
+
hp: 80,
|
|
212
|
+
mp: 30,
|
|
213
|
+
maxHp: 100,
|
|
214
|
+
maxMp: 50,
|
|
215
|
+
level: 5,
|
|
216
|
+
experience: 1200,
|
|
217
|
+
inventory: ["sword", "potion", "potion"],
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
"2.0.0": {
|
|
221
|
+
player: {
|
|
222
|
+
name: "Hero",
|
|
223
|
+
level: 5,
|
|
224
|
+
experience: 1200,
|
|
225
|
+
inventory: ["sword", "potion", "potion"],
|
|
226
|
+
stats: {
|
|
227
|
+
health: 80,
|
|
228
|
+
mana: 30,
|
|
229
|
+
maxHealth: 100,
|
|
230
|
+
maxMana: 50,
|
|
231
|
+
strength: 12,
|
|
232
|
+
intelligence: 8,
|
|
233
|
+
agility: 10,
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
quests: {
|
|
237
|
+
active: ["main_quest_1"],
|
|
238
|
+
completed: ["tutorial"],
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* Example test function to verify migrations work correctly.
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```typescript
|
|
247
|
+
* import { testMigrationChain } from './migrations/example';
|
|
248
|
+
*
|
|
249
|
+
* // In your test file or dev console
|
|
250
|
+
* testMigrationChain();
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
export async function testMigrationChain() {
|
|
254
|
+
const { runMigrations } = await import("./runner");
|
|
255
|
+
console.group("Testing Save Migration Chain");
|
|
256
|
+
// Test 1.0.0 → 3.0.0 (full chain)
|
|
257
|
+
console.log("\nTest 1: Migrating from 1.0.0 to 3.0.0");
|
|
258
|
+
const result1 = runMigrations(testSaves["1.0.0"], "1.0.0", "3.0.0", {
|
|
259
|
+
verbose: true,
|
|
260
|
+
});
|
|
261
|
+
console.log(result1.success ? "✓ Success" : "✗ Failed:", result1.error || result1.data);
|
|
262
|
+
// Test 1.1.0 → 3.0.0 (partial chain)
|
|
263
|
+
console.log("\nTest 2: Migrating from 1.1.0 to 3.0.0");
|
|
264
|
+
const result2 = runMigrations(testSaves["1.1.0"], "1.1.0", "3.0.0");
|
|
265
|
+
console.log(result2.success ? "✓ Success" : "✗ Failed:", result2.error || result2.data);
|
|
266
|
+
// Test 2.0.0 → 3.0.0 (single migration)
|
|
267
|
+
console.log("\nTest 3: Migrating from 2.0.0 to 3.0.0");
|
|
268
|
+
const result3 = runMigrations(testSaves["2.0.0"], "2.0.0", "3.0.0");
|
|
269
|
+
console.log(result3.success ? "✓ Success" : "✗ Failed:", result3.error || result3.data);
|
|
270
|
+
console.groupEnd();
|
|
271
|
+
}
|
|
272
|
+
// ============================================================================
|
|
273
|
+
// USAGE IN YOUR GAME
|
|
274
|
+
// ============================================================================
|
|
275
|
+
/**
|
|
276
|
+
* How to integrate this into your game:
|
|
277
|
+
*
|
|
278
|
+
* 1. Create a migrations file (like this one) in your game
|
|
279
|
+
* 2. Define migration functions for each version change
|
|
280
|
+
* 3. Call registerAllMigrations() after Game.init()
|
|
281
|
+
* 4. Migrations will automatically apply when players load old saves
|
|
282
|
+
*
|
|
283
|
+
* Example:
|
|
284
|
+
*
|
|
285
|
+
* ```typescript
|
|
286
|
+
* // src/index.tsx
|
|
287
|
+
* import { Game } from '@react-text-game/core';
|
|
288
|
+
* import { registerAllMigrations } from './game/migrations';
|
|
289
|
+
*
|
|
290
|
+
* async function init() {
|
|
291
|
+
* await Game.init({
|
|
292
|
+
* gameName: "My Adventure",
|
|
293
|
+
* gameVersion: "3.0.0",
|
|
294
|
+
* isDevMode: import.meta.env.DEV
|
|
295
|
+
* });
|
|
296
|
+
*
|
|
297
|
+
* registerAllMigrations();
|
|
298
|
+
*
|
|
299
|
+
* // Dev mode will validate your migration chain
|
|
300
|
+
* // and warn if there are any issues
|
|
301
|
+
* }
|
|
302
|
+
* ```
|
|
303
|
+
*/
|
|
304
|
+
//# sourceMappingURL=EXAMPLE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EXAMPLE.js","sourceRoot":"","sources":["../../../src/saves/migrations/EXAMPLE.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
export type { MigrationOptions, MigrationResult, SaveMigration, SaveMigrationFn, } from "./types";
|
|
41
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","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;AAClE,YAAY,EACR,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,eAAe,GAClB,MAAM,SAAS,CAAC"}
|
|
@@ -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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"}
|