@trackunit/shared-utils 1.16.10 → 1.16.11

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/index.cjs.js CHANGED
@@ -918,6 +918,13 @@ const validateOrSalvage = ({ rawValue, schema, defaultState, key, onValidationFa
918
918
  * next write, permanently hiding the corruption. Scope it only to keys that are provably
919
919
  * never written by anything other than a plain string.
920
920
  *
921
+ * A legacy raw string that also happens to be valid JSON (e.g. an all-digit username, or the
922
+ * literal text "true"/"false"/"null") parses successfully but as the wrong type (a number,
923
+ * boolean, or null instead of a string) — `JSON.parse` alone can't tell "legacy plain string"
924
+ * apart from "genuinely structured data" here. When `legacyRawString` is set and that first
925
+ * validation attempt fails, this retries validation against the original raw string before
926
+ * falling back to `defaultState`, so a permissive schema still recovers the literal text.
927
+ *
921
928
  * When `migration` is provided, detects versioned envelopes, runs the
922
929
  * migration pipeline, and validates the migrated result. Non-versioned consumers are
923
930
  * unaffected — the migration path is fully opt-in.
@@ -969,6 +976,19 @@ const readFromStorage = ({ storage, key, defaultState, schema, migration, legacy
969
976
  return defaultState;
970
977
  }
971
978
  if (version === undefined) {
979
+ if (legacyRawString) {
980
+ const deserializedResult = schema.safeParse(deserialized);
981
+ if (deserializedResult.success) {
982
+ return deserializedResult.data;
983
+ }
984
+ // The deserialized value parsed as JSON but didn't match the schema (e.g. an all-digit
985
+ // legacy string became a number) — retry against the original raw string before
986
+ // falling back further.
987
+ const rawResult = schema.safeParse(raw);
988
+ if (rawResult.success) {
989
+ return rawResult.data;
990
+ }
991
+ }
972
992
  return validateOrSalvage({
973
993
  rawValue: deserialized,
974
994
  schema,
package/index.esm.js CHANGED
@@ -916,6 +916,13 @@ const validateOrSalvage = ({ rawValue, schema, defaultState, key, onValidationFa
916
916
  * next write, permanently hiding the corruption. Scope it only to keys that are provably
917
917
  * never written by anything other than a plain string.
918
918
  *
919
+ * A legacy raw string that also happens to be valid JSON (e.g. an all-digit username, or the
920
+ * literal text "true"/"false"/"null") parses successfully but as the wrong type (a number,
921
+ * boolean, or null instead of a string) — `JSON.parse` alone can't tell "legacy plain string"
922
+ * apart from "genuinely structured data" here. When `legacyRawString` is set and that first
923
+ * validation attempt fails, this retries validation against the original raw string before
924
+ * falling back to `defaultState`, so a permissive schema still recovers the literal text.
925
+ *
919
926
  * When `migration` is provided, detects versioned envelopes, runs the
920
927
  * migration pipeline, and validates the migrated result. Non-versioned consumers are
921
928
  * unaffected — the migration path is fully opt-in.
@@ -967,6 +974,19 @@ const readFromStorage = ({ storage, key, defaultState, schema, migration, legacy
967
974
  return defaultState;
968
975
  }
969
976
  if (version === undefined) {
977
+ if (legacyRawString) {
978
+ const deserializedResult = schema.safeParse(deserialized);
979
+ if (deserializedResult.success) {
980
+ return deserializedResult.data;
981
+ }
982
+ // The deserialized value parsed as JSON but didn't match the schema (e.g. an all-digit
983
+ // legacy string became a number) — retry against the original raw string before
984
+ // falling back further.
985
+ const rawResult = schema.safeParse(raw);
986
+ if (rawResult.success) {
987
+ return rawResult.data;
988
+ }
989
+ }
970
990
  return validateOrSalvage({
971
991
  rawValue: deserialized,
972
992
  schema,
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry.js","sourceRoot":"","sources":["../../../../../libs/shared/utils/migrations/entry.ts"],"names":[],"mappings":"","sourcesContent":["export {};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/shared-utils",
3
- "version": "1.16.10",
3
+ "version": "1.16.11",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "engines": {
6
6
  "node": ">=24.x"
@@ -24,6 +24,13 @@ import type { MigrationConfig, WebStorageCallbacks } from "./types";
24
24
  * next write, permanently hiding the corruption. Scope it only to keys that are provably
25
25
  * never written by anything other than a plain string.
26
26
  *
27
+ * A legacy raw string that also happens to be valid JSON (e.g. an all-digit username, or the
28
+ * literal text "true"/"false"/"null") parses successfully but as the wrong type (a number,
29
+ * boolean, or null instead of a string) — `JSON.parse` alone can't tell "legacy plain string"
30
+ * apart from "genuinely structured data" here. When `legacyRawString` is set and that first
31
+ * validation attempt fails, this retries validation against the original raw string before
32
+ * falling back to `defaultState`, so a permissive schema still recovers the literal text.
33
+ *
27
34
  * When `migration` is provided, detects versioned envelopes, runs the
28
35
  * migration pipeline, and validates the migrated result. Non-versioned consumers are
29
36
  * unaffected — the migration path is fully opt-in.