Skip to content

Commit 53af77f

Browse files
authored
Merge pull request #578 from StoneCypher/VersionCheckDeserialize_1010
Add version checking to deserialize to refuse future versions
2 parents 464d03b + bebbaa7 commit 53af77f

35 files changed

+1130
-169
lines changed

.gitattributes

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
docs/* linguist-generated=true
2-
build/* linguist-generated=true
3-
dist/* linguist-generated=true
4-
src/ts/generated_code/* linguist-generated=true
5-
benchmark/* linguist-generated=true
6-
coverage/* linguist-generated=true
1+
docs/* linguist-generated
2+
build/* linguist-generated
3+
dist/* linguist-generated
4+
src/ts/generated_code/* linguist-generated
5+
benchmark/* linguist-generated
6+
coverage/* linguist-generated

CHANGELOG.long.md

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
All notable changes to this project will be documented in this file.
44

5-
1190 merges; 210 releases
5+
1192 merges; 210 releases
66

77

88

@@ -18,6 +18,66 @@ Published tags:
1818

1919

2020

21+
 
22+
23+
 
24+
25+
## [Untagged] - 1/8/2026 5:08:43 PM
26+
27+
Commit [94065656f6892f7ebb2e65b990ce334cdef6c8e6](https://github.com/StoneCypher/jssm/commit/94065656f6892f7ebb2e65b990ce334cdef6c8e6)
28+
29+
Author: `John Haugeland <stonecypher@gmail.com>`
30+
31+
* Add version checking to deserialize to refuse future versions
32+
* This commit implements version compatibility checking in the deserialization
33+
process to prevent loading data from future versions of the library.
34+
* Changes made:
35+
* 1. Added compareVersions() utility function
36+
- Compares semantic version strings (e.g., "5.104.2")
37+
- Returns negative/0/positive for less than/equal/greater than
38+
- Handles versions with different numbers of components
39+
- Fully documented with DocBlock comments and examples
40+
* 2. Enhanced deserialize() function
41+
- Now checks if serialized data is from a future version
42+
- Throws clear error message if version is too new
43+
- Error includes both versions and upgrade guidance
44+
- Updated DocBlock documentation with @throws annotation
45+
- Added comprehensive usage examples
46+
* 3. Comprehensive test coverage (14 new tests)
47+
- Tests for refusing future major/minor/patch versions
48+
- Tests for accepting same and older versions
49+
- Tests for error message content
50+
- Tests for successful deserialization with version check
51+
- Tests for compareVersions utility behavior
52+
- All 5075 tests pass successfully
53+
* Why this is important:
54+
- Prevents data corruption from incompatible future formats
55+
- Provides clear error messages to users
56+
- Follows semantic versioning best practices
57+
- Maintains backward compatibility (older versions still load)
58+
* The implementation is defensive and user-friendly, refusing to deserialize
59+
data that might contain features or structures the current version doesn't
60+
understand, while clearly communicating what the user needs to do (upgrade).
61+
* Fixes issue #1010, see https://github.com/StoneCypher/fsl/issues/1010
62+
63+
64+
65+
66+
&nbsp;
67+
68+
&nbsp;
69+
70+
## [Untagged] - 1/8/2026 4:53:35 PM
71+
72+
Commit [464d03b0c0e0141c6e64018155438cb79c276527](https://github.com/StoneCypher/jssm/commit/464d03b0c0e0141c6e64018155438cb79c276527)
73+
74+
Author: `John Haugeland <stonecypher@gmail.com>`
75+
76+
* partial merge
77+
78+
79+
80+
2181
&nbsp;
2282

2383
&nbsp;

CHANGELOG.md

Lines changed: 62 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
All notable changes to this project will be documented in this file.
44

5-
1190 merges; 210 releases; Changlogging the last 10 commits; Full changelog at [CHANGELOG.long.md](CHANGELOG.long.md)
5+
1192 merges; 210 releases; Changlogging the last 10 commits; Full changelog at [CHANGELOG.long.md](CHANGELOG.long.md)
66

77

88

@@ -18,6 +18,66 @@ Published tags:
1818

1919

2020

21+
&nbsp;
22+
23+
&nbsp;
24+
25+
## [Untagged] - 1/8/2026 5:08:43 PM
26+
27+
Commit [94065656f6892f7ebb2e65b990ce334cdef6c8e6](https://github.com/StoneCypher/jssm/commit/94065656f6892f7ebb2e65b990ce334cdef6c8e6)
28+
29+
Author: `John Haugeland <stonecypher@gmail.com>`
30+
31+
* Add version checking to deserialize to refuse future versions
32+
* This commit implements version compatibility checking in the deserialization
33+
process to prevent loading data from future versions of the library.
34+
* Changes made:
35+
* 1. Added compareVersions() utility function
36+
- Compares semantic version strings (e.g., "5.104.2")
37+
- Returns negative/0/positive for less than/equal/greater than
38+
- Handles versions with different numbers of components
39+
- Fully documented with DocBlock comments and examples
40+
* 2. Enhanced deserialize() function
41+
- Now checks if serialized data is from a future version
42+
- Throws clear error message if version is too new
43+
- Error includes both versions and upgrade guidance
44+
- Updated DocBlock documentation with @throws annotation
45+
- Added comprehensive usage examples
46+
* 3. Comprehensive test coverage (14 new tests)
47+
- Tests for refusing future major/minor/patch versions
48+
- Tests for accepting same and older versions
49+
- Tests for error message content
50+
- Tests for successful deserialization with version check
51+
- Tests for compareVersions utility behavior
52+
- All 5075 tests pass successfully
53+
* Why this is important:
54+
- Prevents data corruption from incompatible future formats
55+
- Provides clear error messages to users
56+
- Follows semantic versioning best practices
57+
- Maintains backward compatibility (older versions still load)
58+
* The implementation is defensive and user-friendly, refusing to deserialize
59+
data that might contain features or structures the current version doesn't
60+
understand, while clearly communicating what the user needs to do (upgrade).
61+
* Fixes issue #1010, see https://github.com/StoneCypher/fsl/issues/1010
62+
63+
64+
65+
66+
&nbsp;
67+
68+
&nbsp;
69+
70+
## [Untagged] - 1/8/2026 4:53:35 PM
71+
72+
Commit [464d03b0c0e0141c6e64018155438cb79c276527](https://github.com/StoneCypher/jssm/commit/464d03b0c0e0141c6e64018155438cb79c276527)
73+
74+
Author: `John Haugeland <stonecypher@gmail.com>`
75+
76+
* partial merge
77+
78+
79+
80+
2181
&nbsp;
2282

2383
&nbsp;
@@ -153,36 +213,4 @@ Commit [51d8ae4581a9c2b851fdd441c3c51e89353e2fd2](https://github.com/StoneCypher
153213

154214
Author: `MRR <miguelrios@Miguels-MacBook-Air.local>`
155215

156-
* Remove .js in package.json > export > dafault because it file doesnt exist
157-
158-
159-
160-
161-
&nbsp;
162-
163-
&nbsp;
164-
165-
## [Untagged] - 2/4/2025 5:01:42 PM
166-
167-
Commit [77c87d1279333427cd78386c2a7e3a903fed494e](https://github.com/StoneCypher/jssm/commit/77c87d1279333427cd78386c2a7e3a903fed494e)
168-
169-
Author: `John Haugeland <stonecypher@gmail.com>`
170-
171-
* This branch is bullshit, ignore it, I just need to show off the test environment
172-
173-
174-
175-
176-
&nbsp;
177-
178-
&nbsp;
179-
180-
<a name="5__104__1" />
181-
182-
## [5.104.1] - 10/28/2024 2:26:03 AM
183-
184-
Commit [31dfb9c1f14c6de3cf9ba7773273164c675567c4](https://github.com/StoneCypher/jssm/commit/31dfb9c1f14c6de3cf9ba7773273164c675567c4)
185-
186-
Author: `John Haugeland <stonecypher@gmail.com>`
187-
188-
* remove node 16 from gha over rollup support, move big-3 to node 23 and node 22
216+
* Remove .js in package.json > export > dafault because it file doesnt exist

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,23 @@ Please edit the file it's derived from, instead: `./src/md/readme_base.md`
1818
1919
2020
21-
* Generated for version 5.104.2 at 1/8/2026, 4:51:37 PM
21+
* Generated for version 5.105.0 at 1/8/2026, 5:15:34 PM
2222
2323
-->
24-
# jssm 5.104.2
24+
# jssm 5.105.0
2525

2626
Easy. Small. Fast. TS, es6, es5. Node, Browser. 100% coverage. Property
2727
tests. Fuzz tests. Language tests for a dozen languages and emoji. Easy to
2828
share online. Easy to embed.
2929

3030
Readable, useful state machines as one-liner strings.
3131

32-
***5,072 tests***, run 5,963 times.
32+
***5,084 tests***, run 5,975 times.
3333

34-
* 5,063 specs with 99.9% coverage.
35-
* 9 fuzz tests with 12.4% coverage.
34+
* 5,075 specs with 99.9% coverage.
35+
* 9 fuzz tests with 12.3% coverage.
3636

37-
With 3,010 lines, that's about 1.7 tests per line, or 2.0 generated tests per line.
37+
With 3,028 lines, that's about 1.7 tests per line, or 2.0 generated tests per line.
3838

3939
***Meet your new state machine library.***
4040

dist/deno/README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,23 @@ Please edit the file it's derived from, instead: `./src/md/readme_base.md`
1818
1919
2020
21-
* Generated for version 5.104.2 at 1/8/2026, 4:51:37 PM
21+
* Generated for version 5.105.0 at 1/8/2026, 5:15:34 PM
2222
2323
-->
24-
# jssm 5.104.2
24+
# jssm 5.105.0
2525

2626
Easy. Small. Fast. TS, es6, es5. Node, Browser. 100% coverage. Property
2727
tests. Fuzz tests. Language tests for a dozen languages and emoji. Easy to
2828
share online. Easy to embed.
2929

3030
Readable, useful state machines as one-liner strings.
3131

32-
***5,072 tests***, run 5,963 times.
32+
***5,084 tests***, run 5,975 times.
3333

34-
* 5,063 specs with 99.9% coverage.
35-
* 9 fuzz tests with 12.4% coverage.
34+
* 5,075 specs with 99.9% coverage.
35+
* 9 fuzz tests with 12.3% coverage.
3636

37-
With 3,010 lines, that's about 1.7 tests per line, or 2.0 generated tests per line.
37+
With 3,028 lines, that's about 1.7 tests per line, or 2.0 generated tests per line.
3838

3939
***Meet your new state machine library.***
4040

dist/deno/jssm.d.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,5 +1137,26 @@ declare function from<mDT>(MachineAsString: string, ExtraConstructorFields?: Par
11371137
declare function is_hook_complex_result<mDT>(hr: unknown): hr is HookComplexResult<mDT>;
11381138
declare function is_hook_rejection<mDT>(hr: HookResult<mDT>): boolean;
11391139
declare function abstract_hook_step<mDT>(maybe_hook: HookHandler<mDT> | undefined, hook_args: HookContext<mDT>): HookComplexResult<mDT>;
1140+
/**
1141+
* Deserializes a previously serialized machine state.
1142+
*
1143+
* This function recreates a machine from a serialization object, restoring its
1144+
* state, data, and history. For security and compatibility reasons, it will
1145+
* refuse to deserialize data from future versions of the library.
1146+
*
1147+
* @typeparam mDT - The type of the machine data member
1148+
*
1149+
* @param {string} machine_string - The FSL string defining the machine structure
1150+
* @param {JssmSerialization<mDT>} ser - The serialization object to restore from
1151+
*
1152+
* @returns {Machine<mDT>} - The restored machine instance
1153+
*
1154+
* @throws {Error} If the serialization is from a future version
1155+
*
1156+
* @example
1157+
* const machine = jssm.from("a -> b;");
1158+
* const serialized = machine.serialize();
1159+
* const restored = jssm.deserialize("a -> b;", serialized);
1160+
*/
11401161
declare function deserialize<mDT>(machine_string: string, ser: JssmSerialization<mDT>): Machine<mDT>;
11411162
export { version, build_time, transfer_state_properties, Machine, deserialize, make, wrap_parse as parse, compile, sm, from, arrow_direction, arrow_left_kind, arrow_right_kind, seq, unique, find_repeated, weighted_rand_select, histograph, weighted_sample_select, weighted_histo_key, sleep, constants, shapes, gviz_shapes, named_colors, is_hook_rejection, is_hook_complex_result, abstract_hook_step, state_style_condense, FslDirections };

dist/deno/jssm.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/es6/jssm.d.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,5 +1137,26 @@ declare function from<mDT>(MachineAsString: string, ExtraConstructorFields?: Par
11371137
declare function is_hook_complex_result<mDT>(hr: unknown): hr is HookComplexResult<mDT>;
11381138
declare function is_hook_rejection<mDT>(hr: HookResult<mDT>): boolean;
11391139
declare function abstract_hook_step<mDT>(maybe_hook: HookHandler<mDT> | undefined, hook_args: HookContext<mDT>): HookComplexResult<mDT>;
1140+
/**
1141+
* Deserializes a previously serialized machine state.
1142+
*
1143+
* This function recreates a machine from a serialization object, restoring its
1144+
* state, data, and history. For security and compatibility reasons, it will
1145+
* refuse to deserialize data from future versions of the library.
1146+
*
1147+
* @typeparam mDT - The type of the machine data member
1148+
*
1149+
* @param {string} machine_string - The FSL string defining the machine structure
1150+
* @param {JssmSerialization<mDT>} ser - The serialization object to restore from
1151+
*
1152+
* @returns {Machine<mDT>} - The restored machine instance
1153+
*
1154+
* @throws {Error} If the serialization is from a future version
1155+
*
1156+
* @example
1157+
* const machine = jssm.from("a -> b;");
1158+
* const serialized = machine.serialize();
1159+
* const restored = jssm.deserialize("a -> b;", serialized);
1160+
*/
11401161
declare function deserialize<mDT>(machine_string: string, ser: JssmSerialization<mDT>): Machine<mDT>;
11411162
export { version, build_time, transfer_state_properties, Machine, deserialize, make, wrap_parse as parse, compile, sm, from, arrow_direction, arrow_left_kind, arrow_right_kind, seq, unique, find_repeated, weighted_rand_select, histograph, weighted_sample_select, weighted_histo_key, sleep, constants, shapes, gviz_shapes, named_colors, is_hook_rejection, is_hook_complex_result, abstract_hook_step, state_style_condense, FslDirections };

dist/es6/jssm.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2475,7 +2475,62 @@ function abstract_hook_step(maybe_hook, hook_args) {
24752475
return { pass: true };
24762476
}
24772477
}
2478+
/**
2479+
* Compares two semantic version strings.
2480+
*
2481+
* @param {string} v1 - First version string (e.g., "5.104.2")
2482+
* @param {string} v2 - Second version string (e.g., "5.103.1")
2483+
*
2484+
* @returns {number} - Negative if v1 < v2, 0 if equal, positive if v1 > v2
2485+
*
2486+
* @example
2487+
* compareVersions("5.104.2", "5.103.1") // returns 1 (v1 is newer)
2488+
*
2489+
* @example
2490+
* compareVersions("5.104.2", "6.0.0") // returns -1 (v1 is older)
2491+
*
2492+
* @example
2493+
* compareVersions("5.104.2", "5.104.2") // returns 0 (equal)
2494+
*/
2495+
function compareVersions(v1, v2) {
2496+
const parts1 = v1.split('.').map(Number);
2497+
const parts2 = v2.split('.').map(Number);
2498+
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
2499+
const num1 = parts1[i] || 0;
2500+
const num2 = parts2[i] || 0;
2501+
if (num1 !== num2) {
2502+
return num1 - num2;
2503+
}
2504+
}
2505+
return 0;
2506+
}
2507+
/**
2508+
* Deserializes a previously serialized machine state.
2509+
*
2510+
* This function recreates a machine from a serialization object, restoring its
2511+
* state, data, and history. For security and compatibility reasons, it will
2512+
* refuse to deserialize data from future versions of the library.
2513+
*
2514+
* @typeparam mDT - The type of the machine data member
2515+
*
2516+
* @param {string} machine_string - The FSL string defining the machine structure
2517+
* @param {JssmSerialization<mDT>} ser - The serialization object to restore from
2518+
*
2519+
* @returns {Machine<mDT>} - The restored machine instance
2520+
*
2521+
* @throws {Error} If the serialization is from a future version
2522+
*
2523+
* @example
2524+
* const machine = jssm.from("a -> b;");
2525+
* const serialized = machine.serialize();
2526+
* const restored = jssm.deserialize("a -> b;", serialized);
2527+
*/
24782528
function deserialize(machine_string, ser) {
2529+
// Refuse to deserialize data from future versions
2530+
if (compareVersions(ser.jssm_version, version) > 0) {
2531+
throw new Error(`Cannot deserialize from future version ${ser.jssm_version} ` +
2532+
`(current version is ${version}). Please upgrade jssm to deserialize this data.`);
2533+
}
24792534
const machine = from(machine_string, { data: ser.data, history: ser.history_capacity });
24802535
machine._state = ser.state;
24812536
ser.history.forEach(history_item => machine._history.push(history_item));

dist/es6/version.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
const version = "5.104.2", build_time = 1767919734221;
1+
const version = "5.105.0", build_time = 1767921147289;
22
export { version, build_time };

0 commit comments

Comments
 (0)