xstate 6.0.0-alpha.32 → 6.0.0-alpha.33

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.
@@ -7,7 +7,7 @@ export { createMachineFromConfig } from "./createMachineFromConfig.js";
7
7
  export type { ActionJSON, GuardJSON, InvokeJSON, MachineJSON, StateNodeJSON, TransitionJSON } from "./createMachineFromConfig.js";
8
8
  export { machineConfigToJSON, serializeMachine, type CodeExpression } from "./serialize.js";
9
9
  export { mapState } from "./mapState.js";
10
- export { machineVersions, migrateSnapshot, type MachineVersionsOptions, type ParsedPersistedSnapshot, type PersistedMachineIdentity, type PersistedSnapshotDataFrom, type SnapshotMigrationHandlers, type PersistedSnapshotFrom } from "./machineVersions.js";
10
+ export { machineVersions, type MigrateSnapshotOptions, type MachineVersionsOptions, type ParsedPersistedSnapshot, type PersistedMachineIdentity, type PersistedSnapshotDataFrom, type PersistedSnapshotSource, type SnapshotMigrationHandlers, type PersistedSnapshotFrom } from "./machineVersions.js";
11
11
  export { types, isTypeSchema, type StandardSchemaV1, type TypeSchema } from "./schema.types.js";
12
12
  export type { ActorValidationBoundary, ActorValidationEventOrigin, ActorValidationRequest, ActorLogicValidator } from "./validation.types.js";
13
13
  export { createSystem, setup } from "./setup.js";
@@ -26,26 +26,30 @@ export type ParsedPersistedSnapshot<TMachines extends readonly AnyStateMachine[]
26
26
  export type MachineVersionsOptions<TMachines extends readonly AnyStateMachine[]> = {
27
27
  unversioned?: NonNullable<TMachines[number]['version']>;
28
28
  };
29
- type ParsedSnapshotSource = {
30
- machine: VersionedStateMachine;
31
- snapshot: Snapshot<unknown> & {
32
- context: unknown;
33
- machine: PersistedMachineIdentity;
34
- [key: string]: unknown;
35
- };
36
- };
37
- type SourceVersion<TSource extends ParsedSnapshotSource> = TSource extends unknown ? NonNullable<TSource['machine']['version']> : never;
38
- type SourceForVersion<TSource extends ParsedSnapshotSource, TVersion extends string> = TSource extends unknown ? TSource['machine']['version'] extends TVersion ? TSource : never : never;
39
- export type SnapshotMigrationHandlers<TSource extends ParsedSnapshotSource, TTarget extends AnyStateMachine> = {
40
- [TVersion in SourceVersion<TSource>]?: (snapshot: SourceForVersion<TSource, TVersion>['snapshot']) => PersistedSnapshotDataFrom<TTarget>;
29
+ type MaybePromise<T> = T | PromiseLike<T>;
30
+ type MachineVersion<TMachines extends readonly VersionedStateMachine[]> = TMachines[number]['version'];
31
+ type MachineForVersion<TMachines extends readonly VersionedStateMachine[], TVersion extends string> = {
32
+ [K in keyof TMachines]: TMachines[K] extends VersionedStateMachine ? TMachines[K]['version'] extends TVersion ? TMachines[K] : never : never;
33
+ }[number];
34
+ export type PersistedSnapshotSource = {
35
+ id?: string;
36
+ version?: string;
37
+ };
38
+ export type SnapshotMigrationHandlers<TMachines extends readonly VersionedStateMachine[], TTarget extends VersionedStateMachine> = {
39
+ [TVersion in Exclude<MachineVersion<TMachines>, TTarget['version']>]?: (snapshot: PersistedSnapshotFrom<MachineForVersion<TMachines, TVersion>>) => MaybePromise<PersistedSnapshotDataFrom<TTarget>>;
40
+ } & {
41
+ '*'?: (snapshot: unknown, source: PersistedSnapshotSource) => MaybePromise<PersistedSnapshotDataFrom<TTarget>>;
42
+ };
43
+ export type MigrateSnapshotOptions<TMachines extends readonly VersionedStateMachine[], TTargetVersion extends MachineVersion<TMachines>> = {
44
+ to: TTargetVersion;
45
+ migrations: SnapshotMigrationHandlers<TMachines, MachineForVersion<TMachines, TTargetVersion>>;
41
46
  };
42
47
  /** Creates parsers backed by retained versions of one machine. */
43
48
  export declare function machineVersions<const TMachines extends readonly [
44
49
  VersionedStateMachine,
45
50
  ...VersionedStateMachine[]
46
51
  ]>(machines: TMachines, options?: MachineVersionsOptions<TMachines>): {
47
- parseSnapshot(raw: unknown): Promise<ParsedPersistedSnapshot<TMachines>>;
52
+ parseSnapshot: (raw: unknown) => Promise<ParsedPersistedSnapshot<TMachines>>;
53
+ migrateSnapshot<TTargetVersion extends MachineVersion<TMachines>>(raw: unknown, migrationOptions: MigrateSnapshotOptions<TMachines, TTargetVersion>): Promise<PersistedSnapshotFrom<MachineForVersion<TMachines, TTargetVersion>>>;
48
54
  };
49
- /** Migrates a parsed persisted snapshot directly to a target machine. */
50
- export declare function migrateSnapshot<TSource extends ParsedSnapshotSource, TTarget extends VersionedStateMachine>(source: TSource, target: TTarget, migrations: SnapshotMigrationHandlers<TSource, TTarget>): Promise<PersistedSnapshotFrom<TTarget>>;
51
55
  export {};
@@ -1970,6 +1970,21 @@ function mapState(snapshot, mapper) {
1970
1970
  function isObject(value) {
1971
1971
  return value !== null && typeof value === 'object';
1972
1972
  }
1973
+ function getSnapshotSource(raw, defaultId) {
1974
+ if (!isObject(raw)) {
1975
+ return {};
1976
+ }
1977
+ if (isObject(raw.machine)) {
1978
+ return {
1979
+ id: typeof raw.machine.id === 'string' ? raw.machine.id : undefined,
1980
+ version: typeof raw.machine.version === 'string' ? raw.machine.version : undefined
1981
+ };
1982
+ }
1983
+ return {
1984
+ id: typeof raw.version === 'string' ? defaultId : undefined,
1985
+ version: typeof raw.version === 'string' ? raw.version : undefined
1986
+ };
1987
+ }
1973
1988
  async function validate(schema, value, description) {
1974
1989
  if (!schema) {
1975
1990
  return value;
@@ -1980,6 +1995,18 @@ async function validate(schema, value, description) {
1980
1995
  }
1981
1996
  return result.value;
1982
1997
  }
1998
+ async function finalizeSnapshot(snapshot, target) {
1999
+ const context = await validate(target.schemas?.context, snapshot.context, `context for machine '${target.id}' version '${target.version}'`);
2000
+ return {
2001
+ ...snapshot,
2002
+ context,
2003
+ machine: {
2004
+ id: target.id,
2005
+ version: target.version
2006
+ },
2007
+ version: target.version
2008
+ };
2009
+ }
1983
2010
 
1984
2011
  /** Creates parsers backed by retained versions of one machine. */
1985
2012
  function machineVersions(machines, options) {
@@ -1989,6 +2016,9 @@ function machineVersions(machines, options) {
1989
2016
  if (machine.version === undefined) {
1990
2017
  throw new Error(`Machine '${machine.id}' must define a version.`);
1991
2018
  }
2019
+ if (machine.version === '*') {
2020
+ throw new Error("Machine version '*' is reserved for wildcard migrations.");
2021
+ }
1992
2022
  if (machine.id !== machineId) {
1993
2023
  throw new Error(`Machine '${machine.id}' does not match machine ID '${machineId}'.`);
1994
2024
  }
@@ -2001,83 +2031,85 @@ function machineVersions(machines, options) {
2001
2031
  if (options?.unversioned !== undefined && !byIdentity.has(`${machineId}\0${options.unversioned}`)) {
2002
2032
  throw new Error(`Unversioned snapshot version '${options.unversioned}' is not retained for machine '${machineId}'.`);
2003
2033
  }
2004
- return {
2005
- async parseSnapshot(raw) {
2006
- if (!isObject(raw)) {
2007
- throw new Error('Persisted snapshot is missing machine identity.');
2008
- }
2009
- let id;
2010
- let version;
2011
- if (isObject(raw.machine)) {
2012
- ({
2034
+ const parseSnapshot = async raw => {
2035
+ if (!isObject(raw)) {
2036
+ throw new Error('Persisted snapshot is missing machine identity.');
2037
+ }
2038
+ let id;
2039
+ let version;
2040
+ if (isObject(raw.machine)) {
2041
+ ({
2042
+ id,
2043
+ version
2044
+ } = raw.machine);
2045
+ if (typeof id !== 'string' || typeof version !== 'string') {
2046
+ throw new Error('Persisted snapshot has an invalid machine identity.');
2047
+ }
2048
+ } else if (typeof raw.version === 'string') {
2049
+ id = machineId;
2050
+ version = raw.version;
2051
+ } else if (raw.version === undefined && options?.unversioned !== undefined) {
2052
+ id = machineId;
2053
+ version = options.unversioned;
2054
+ } else {
2055
+ throw new Error('Persisted snapshot is missing machine identity.');
2056
+ }
2057
+ if (raw.version !== undefined && typeof raw.version !== 'string') {
2058
+ throw new Error('Persisted snapshot has an invalid version.');
2059
+ }
2060
+ if (raw.version !== undefined && raw.version !== version) {
2061
+ throw new Error(`Persisted snapshot version '${raw.version}' conflicts with machine version '${version}'.`);
2062
+ }
2063
+ const machine = byIdentity.get(`${id}\0${version}`);
2064
+ if (!machine) {
2065
+ throw new Error(`Unknown machine identity '${id}' version '${version}'.`);
2066
+ }
2067
+ const context = await validate(machine.schemas?.context, raw.context, `context for machine '${id}' version '${version}'`);
2068
+ return {
2069
+ machine,
2070
+ snapshot: {
2071
+ ...raw,
2072
+ context,
2073
+ machine: {
2013
2074
  id,
2014
2075
  version
2015
- } = raw.machine);
2016
- if (typeof id !== 'string' || typeof version !== 'string') {
2017
- throw new Error('Persisted snapshot has an invalid machine identity.');
2018
2076
  }
2019
- } else if (typeof raw.version === 'string') {
2020
- id = machineId;
2021
- version = raw.version;
2022
- } else if (raw.version === undefined && options?.unversioned !== undefined) {
2023
- id = machineId;
2024
- version = options.unversioned;
2025
- } else {
2026
- throw new Error('Persisted snapshot is missing machine identity.');
2027
2077
  }
2028
- if (raw.version !== undefined && typeof raw.version !== 'string') {
2029
- throw new Error('Persisted snapshot has an invalid version.');
2078
+ };
2079
+ };
2080
+ return {
2081
+ parseSnapshot,
2082
+ async migrateSnapshot(raw, migrationOptions) {
2083
+ const target = byIdentity.get(`${machineId}\0${migrationOptions.to}`);
2084
+ if (!target) {
2085
+ throw new Error(`Target version '${migrationOptions.to}' is not retained for machine '${machineId}'.`);
2030
2086
  }
2031
- if (raw.version !== undefined && raw.version !== version) {
2032
- throw new Error(`Persisted snapshot version '${raw.version}' conflicts with machine version '${version}'.`);
2087
+ let source;
2088
+ let parseError;
2089
+ try {
2090
+ source = await parseSnapshot(raw);
2091
+ } catch (error) {
2092
+ parseError = error;
2033
2093
  }
2034
- const machine = byIdentity.get(`${id}\0${version}`);
2035
- if (!machine) {
2036
- throw new Error(`Unknown machine identity '${id}' version '${version}'.`);
2094
+ if (source) {
2095
+ if (source.machine.version === target.version) {
2096
+ return finalizeSnapshot(source.snapshot, target);
2097
+ }
2098
+ const exactMigration = migrationOptions.migrations[source.machine.version];
2099
+ if (exactMigration) {
2100
+ return finalizeSnapshot(await exactMigration(source.snapshot), target);
2101
+ }
2037
2102
  }
2038
- const context = await validate(machine.schemas?.context, raw.context, `context for machine '${id}' version '${version}'`);
2039
- return {
2040
- machine,
2041
- snapshot: {
2042
- ...raw,
2043
- context,
2044
- machine: {
2045
- id,
2046
- version
2047
- }
2103
+ const wildcardMigration = migrationOptions.migrations['*'];
2104
+ if (!wildcardMigration) {
2105
+ if (source) {
2106
+ throw new Error(`No snapshot migration from version '${source.machine.version}' to '${target.version}' for machine '${target.id}'.`);
2048
2107
  }
2049
- };
2050
- }
2051
- };
2052
- }
2053
-
2054
- /** Migrates a parsed persisted snapshot directly to a target machine. */
2055
- async function migrateSnapshot(source, target, migrations) {
2056
- if (target.version === undefined) {
2057
- throw new Error(`Target machine '${target.id}' must define a version.`);
2058
- }
2059
- if (source.machine.id !== target.id) {
2060
- throw new Error(`Cannot migrate machine '${source.machine.id}' to '${target.id}'.`);
2061
- }
2062
- let snapshot;
2063
- if (source.machine.version === target.version) {
2064
- snapshot = source.snapshot;
2065
- } else {
2066
- const migrate = migrations[source.machine.version];
2067
- if (!migrate) {
2068
- throw new Error(`No snapshot migration from version '${source.machine.version}' to '${target.version}' for machine '${target.id}'.`);
2108
+ throw parseError;
2109
+ }
2110
+ const snapshot = await wildcardMigration(raw, getSnapshotSource(raw, machineId));
2111
+ return finalizeSnapshot(snapshot, target);
2069
2112
  }
2070
- snapshot = migrate(source.snapshot);
2071
- }
2072
- const context = await validate(target.schemas?.context, snapshot.context, `context for machine '${target.id}' version '${target.version}'`);
2073
- return {
2074
- ...snapshot,
2075
- context,
2076
- machine: {
2077
- id: target.id,
2078
- version: target.version
2079
- },
2080
- version: target.version
2081
2113
  };
2082
2114
  }
2083
2115
 
@@ -2881,7 +2913,6 @@ exports.isTypeSchema = isTypeSchema;
2881
2913
  exports.machineConfigToJSON = machineConfigToJSON;
2882
2914
  exports.machineVersions = machineVersions;
2883
2915
  exports.mapState = mapState;
2884
- exports.migrateSnapshot = migrateSnapshot;
2885
2916
  exports.serializeMachine = serializeMachine;
2886
2917
  exports.setup = setup;
2887
2918
  exports.toPromise = toPromise;
@@ -38,7 +38,6 @@ export {
38
38
  machineVersions,
39
39
  mapState,
40
40
  matchesState,
41
- migrateSnapshot,
42
41
  pathToStateValue,
43
42
  serializeMachine,
44
43
  setup,
@@ -1970,6 +1970,21 @@ function mapState(snapshot, mapper) {
1970
1970
  function isObject(value) {
1971
1971
  return value !== null && typeof value === 'object';
1972
1972
  }
1973
+ function getSnapshotSource(raw, defaultId) {
1974
+ if (!isObject(raw)) {
1975
+ return {};
1976
+ }
1977
+ if (isObject(raw.machine)) {
1978
+ return {
1979
+ id: typeof raw.machine.id === 'string' ? raw.machine.id : undefined,
1980
+ version: typeof raw.machine.version === 'string' ? raw.machine.version : undefined
1981
+ };
1982
+ }
1983
+ return {
1984
+ id: typeof raw.version === 'string' ? defaultId : undefined,
1985
+ version: typeof raw.version === 'string' ? raw.version : undefined
1986
+ };
1987
+ }
1973
1988
  async function validate(schema, value, description) {
1974
1989
  if (!schema) {
1975
1990
  return value;
@@ -1980,6 +1995,18 @@ async function validate(schema, value, description) {
1980
1995
  }
1981
1996
  return result.value;
1982
1997
  }
1998
+ async function finalizeSnapshot(snapshot, target) {
1999
+ const context = await validate(target.schemas?.context, snapshot.context, `context for machine '${target.id}' version '${target.version}'`);
2000
+ return {
2001
+ ...snapshot,
2002
+ context,
2003
+ machine: {
2004
+ id: target.id,
2005
+ version: target.version
2006
+ },
2007
+ version: target.version
2008
+ };
2009
+ }
1983
2010
 
1984
2011
  /** Creates parsers backed by retained versions of one machine. */
1985
2012
  function machineVersions(machines, options) {
@@ -1989,6 +2016,9 @@ function machineVersions(machines, options) {
1989
2016
  if (machine.version === undefined) {
1990
2017
  throw new Error(`Machine '${machine.id}' must define a version.`);
1991
2018
  }
2019
+ if (machine.version === '*') {
2020
+ throw new Error("Machine version '*' is reserved for wildcard migrations.");
2021
+ }
1992
2022
  if (machine.id !== machineId) {
1993
2023
  throw new Error(`Machine '${machine.id}' does not match machine ID '${machineId}'.`);
1994
2024
  }
@@ -2001,83 +2031,85 @@ function machineVersions(machines, options) {
2001
2031
  if (options?.unversioned !== undefined && !byIdentity.has(`${machineId}\0${options.unversioned}`)) {
2002
2032
  throw new Error(`Unversioned snapshot version '${options.unversioned}' is not retained for machine '${machineId}'.`);
2003
2033
  }
2004
- return {
2005
- async parseSnapshot(raw) {
2006
- if (!isObject(raw)) {
2007
- throw new Error('Persisted snapshot is missing machine identity.');
2008
- }
2009
- let id;
2010
- let version;
2011
- if (isObject(raw.machine)) {
2012
- ({
2034
+ const parseSnapshot = async raw => {
2035
+ if (!isObject(raw)) {
2036
+ throw new Error('Persisted snapshot is missing machine identity.');
2037
+ }
2038
+ let id;
2039
+ let version;
2040
+ if (isObject(raw.machine)) {
2041
+ ({
2042
+ id,
2043
+ version
2044
+ } = raw.machine);
2045
+ if (typeof id !== 'string' || typeof version !== 'string') {
2046
+ throw new Error('Persisted snapshot has an invalid machine identity.');
2047
+ }
2048
+ } else if (typeof raw.version === 'string') {
2049
+ id = machineId;
2050
+ version = raw.version;
2051
+ } else if (raw.version === undefined && options?.unversioned !== undefined) {
2052
+ id = machineId;
2053
+ version = options.unversioned;
2054
+ } else {
2055
+ throw new Error('Persisted snapshot is missing machine identity.');
2056
+ }
2057
+ if (raw.version !== undefined && typeof raw.version !== 'string') {
2058
+ throw new Error('Persisted snapshot has an invalid version.');
2059
+ }
2060
+ if (raw.version !== undefined && raw.version !== version) {
2061
+ throw new Error(`Persisted snapshot version '${raw.version}' conflicts with machine version '${version}'.`);
2062
+ }
2063
+ const machine = byIdentity.get(`${id}\0${version}`);
2064
+ if (!machine) {
2065
+ throw new Error(`Unknown machine identity '${id}' version '${version}'.`);
2066
+ }
2067
+ const context = await validate(machine.schemas?.context, raw.context, `context for machine '${id}' version '${version}'`);
2068
+ return {
2069
+ machine,
2070
+ snapshot: {
2071
+ ...raw,
2072
+ context,
2073
+ machine: {
2013
2074
  id,
2014
2075
  version
2015
- } = raw.machine);
2016
- if (typeof id !== 'string' || typeof version !== 'string') {
2017
- throw new Error('Persisted snapshot has an invalid machine identity.');
2018
2076
  }
2019
- } else if (typeof raw.version === 'string') {
2020
- id = machineId;
2021
- version = raw.version;
2022
- } else if (raw.version === undefined && options?.unversioned !== undefined) {
2023
- id = machineId;
2024
- version = options.unversioned;
2025
- } else {
2026
- throw new Error('Persisted snapshot is missing machine identity.');
2027
2077
  }
2028
- if (raw.version !== undefined && typeof raw.version !== 'string') {
2029
- throw new Error('Persisted snapshot has an invalid version.');
2078
+ };
2079
+ };
2080
+ return {
2081
+ parseSnapshot,
2082
+ async migrateSnapshot(raw, migrationOptions) {
2083
+ const target = byIdentity.get(`${machineId}\0${migrationOptions.to}`);
2084
+ if (!target) {
2085
+ throw new Error(`Target version '${migrationOptions.to}' is not retained for machine '${machineId}'.`);
2030
2086
  }
2031
- if (raw.version !== undefined && raw.version !== version) {
2032
- throw new Error(`Persisted snapshot version '${raw.version}' conflicts with machine version '${version}'.`);
2087
+ let source;
2088
+ let parseError;
2089
+ try {
2090
+ source = await parseSnapshot(raw);
2091
+ } catch (error) {
2092
+ parseError = error;
2033
2093
  }
2034
- const machine = byIdentity.get(`${id}\0${version}`);
2035
- if (!machine) {
2036
- throw new Error(`Unknown machine identity '${id}' version '${version}'.`);
2094
+ if (source) {
2095
+ if (source.machine.version === target.version) {
2096
+ return finalizeSnapshot(source.snapshot, target);
2097
+ }
2098
+ const exactMigration = migrationOptions.migrations[source.machine.version];
2099
+ if (exactMigration) {
2100
+ return finalizeSnapshot(await exactMigration(source.snapshot), target);
2101
+ }
2037
2102
  }
2038
- const context = await validate(machine.schemas?.context, raw.context, `context for machine '${id}' version '${version}'`);
2039
- return {
2040
- machine,
2041
- snapshot: {
2042
- ...raw,
2043
- context,
2044
- machine: {
2045
- id,
2046
- version
2047
- }
2103
+ const wildcardMigration = migrationOptions.migrations['*'];
2104
+ if (!wildcardMigration) {
2105
+ if (source) {
2106
+ throw new Error(`No snapshot migration from version '${source.machine.version}' to '${target.version}' for machine '${target.id}'.`);
2048
2107
  }
2049
- };
2050
- }
2051
- };
2052
- }
2053
-
2054
- /** Migrates a parsed persisted snapshot directly to a target machine. */
2055
- async function migrateSnapshot(source, target, migrations) {
2056
- if (target.version === undefined) {
2057
- throw new Error(`Target machine '${target.id}' must define a version.`);
2058
- }
2059
- if (source.machine.id !== target.id) {
2060
- throw new Error(`Cannot migrate machine '${source.machine.id}' to '${target.id}'.`);
2061
- }
2062
- let snapshot;
2063
- if (source.machine.version === target.version) {
2064
- snapshot = source.snapshot;
2065
- } else {
2066
- const migrate = migrations[source.machine.version];
2067
- if (!migrate) {
2068
- throw new Error(`No snapshot migration from version '${source.machine.version}' to '${target.version}' for machine '${target.id}'.`);
2108
+ throw parseError;
2109
+ }
2110
+ const snapshot = await wildcardMigration(raw, getSnapshotSource(raw, machineId));
2111
+ return finalizeSnapshot(snapshot, target);
2069
2112
  }
2070
- snapshot = migrate(source.snapshot);
2071
- }
2072
- const context = await validate(target.schemas?.context, snapshot.context, `context for machine '${target.id}' version '${target.version}'`);
2073
- return {
2074
- ...snapshot,
2075
- context,
2076
- machine: {
2077
- id: target.id,
2078
- version: target.version
2079
- },
2080
- version: target.version
2081
2113
  };
2082
2114
  }
2083
2115
 
@@ -2884,7 +2916,6 @@ exports.isTypeSchema = isTypeSchema;
2884
2916
  exports.machineConfigToJSON = machineConfigToJSON;
2885
2917
  exports.machineVersions = machineVersions;
2886
2918
  exports.mapState = mapState;
2887
- exports.migrateSnapshot = migrateSnapshot;
2888
2919
  exports.serializeMachine = serializeMachine;
2889
2920
  exports.setup = setup;
2890
2921
  exports.toPromise = toPromise;
@@ -38,7 +38,6 @@ export {
38
38
  machineVersions,
39
39
  mapState,
40
40
  matchesState,
41
- migrateSnapshot,
42
41
  pathToStateValue,
43
42
  serializeMachine,
44
43
  setup,