@react-text-game/core 0.2.1 → 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.
Files changed (37) hide show
  1. package/README.md +2 -0
  2. package/dist/game.d.ts.map +1 -1
  3. package/dist/game.js +13 -0
  4. package/dist/game.js.map +1 -1
  5. package/dist/saves/hooks/useLoadGame.d.ts +3 -0
  6. package/dist/saves/hooks/useLoadGame.d.ts.map +1 -1
  7. package/dist/saves/hooks/useLoadGame.js +23 -1
  8. package/dist/saves/hooks/useLoadGame.js.map +1 -1
  9. package/dist/saves/index.d.ts +1 -0
  10. package/dist/saves/index.d.ts.map +1 -1
  11. package/dist/saves/index.js +1 -0
  12. package/dist/saves/index.js.map +1 -1
  13. package/dist/saves/migrations/EXAMPLE.d.ts +113 -0
  14. package/dist/saves/migrations/EXAMPLE.d.ts.map +1 -0
  15. package/dist/saves/migrations/EXAMPLE.js +304 -0
  16. package/dist/saves/migrations/EXAMPLE.js.map +1 -0
  17. package/dist/saves/migrations/index.d.ts +41 -0
  18. package/dist/saves/migrations/index.d.ts.map +1 -0
  19. package/dist/saves/migrations/index.js +40 -0
  20. package/dist/saves/migrations/index.js.map +1 -0
  21. package/dist/saves/migrations/registry.d.ts +72 -0
  22. package/dist/saves/migrations/registry.d.ts.map +1 -0
  23. package/dist/saves/migrations/registry.js +174 -0
  24. package/dist/saves/migrations/registry.js.map +1 -0
  25. package/dist/saves/migrations/runner.d.ts +45 -0
  26. package/dist/saves/migrations/runner.d.ts.map +1 -0
  27. package/dist/saves/migrations/runner.js +139 -0
  28. package/dist/saves/migrations/runner.js.map +1 -0
  29. package/dist/saves/migrations/types.d.ts +182 -0
  30. package/dist/saves/migrations/types.d.ts.map +1 -0
  31. package/dist/saves/migrations/types.js +2 -0
  32. package/dist/saves/migrations/types.js.map +1 -0
  33. package/dist/tests/migrations.test.d.ts +2 -0
  34. package/dist/tests/migrations.test.d.ts.map +1 -0
  35. package/dist/tests/migrations.test.js +602 -0
  36. package/dist/tests/migrations.test.js.map +1 -0
  37. package/package.json +3 -4
@@ -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