@zelgadis87/utils-core 5.4.4 → 5.4.6
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/.rollup/index.cjs +234 -2
- package/.rollup/index.cjs.map +1 -1
- package/.rollup/index.d.ts +188 -3
- package/.rollup/index.mjs +234 -3
- package/.rollup/index.mjs.map +1 -1
- package/.rollup/tsconfig.tsbuildinfo +1 -1
- package/CHANGELOG.md +13 -0
- package/package.json +2 -1
- package/src/upgrade/DataUpgrader.ts +109 -4
- package/src/upgrade/DataUpgraderBuilder.ts +168 -0
- package/src/upgrade/_index.ts +1 -0
- package/src/utils/css.ts +1 -1
- package/src/utils/operations.ts +27 -1
package/.rollup/index.cjs
CHANGED
|
@@ -1361,6 +1361,31 @@ const Operation = {
|
|
|
1361
1361
|
partition: (results) => {
|
|
1362
1362
|
return partition(results, r => r.success);
|
|
1363
1363
|
},
|
|
1364
|
+
/**
|
|
1365
|
+
* Transforms the success value of an operation using the provided function.
|
|
1366
|
+
* If the operation is a failure, it is returned unchanged (same reference).
|
|
1367
|
+
* The transformation function is never called on a failure.
|
|
1368
|
+
*
|
|
1369
|
+
* @template T - The input success data type
|
|
1370
|
+
* @template U - The output success data type
|
|
1371
|
+
* @template E - The error type (preserved exactly)
|
|
1372
|
+
* @param op - The operation result to transform
|
|
1373
|
+
* @param fn - Pure function applied to the success value
|
|
1374
|
+
* @returns A new success with the transformed value, or the original failure
|
|
1375
|
+
*
|
|
1376
|
+
* @example
|
|
1377
|
+
* Operation.map(Operation.ok(1), x => x * 2);
|
|
1378
|
+
* // => { success: true, data: 2 }
|
|
1379
|
+
*
|
|
1380
|
+
* Operation.map(Operation.ko('err'), x => x * 2);
|
|
1381
|
+
* // => { success: false, error: 'err' }
|
|
1382
|
+
*/
|
|
1383
|
+
map: (op, fn) => {
|
|
1384
|
+
if (op.success) {
|
|
1385
|
+
return { success: true, data: fn(op.data) };
|
|
1386
|
+
}
|
|
1387
|
+
return op;
|
|
1388
|
+
},
|
|
1364
1389
|
combine: combine$1,
|
|
1365
1390
|
};
|
|
1366
1391
|
|
|
@@ -3691,11 +3716,70 @@ function printTransitions(transitions) {
|
|
|
3691
3716
|
}
|
|
3692
3717
|
|
|
3693
3718
|
const VERSION_FIELD = "$version";
|
|
3719
|
+
/**
|
|
3720
|
+
* Versioned data migration system for transparently upgrading serialized data to the latest version.
|
|
3721
|
+
*
|
|
3722
|
+
* DataUpgrader manages the migration of data structures across multiple versions, automatically
|
|
3723
|
+
* finding the shortest upgrade path and applying transformations. This is particularly useful for
|
|
3724
|
+
* handling persisted data that may be in any historical format.
|
|
3725
|
+
*
|
|
3726
|
+
* **Key Use Case:** Reading serialized data of unknown version and migrating it transparently.
|
|
3727
|
+
*
|
|
3728
|
+
* @example
|
|
3729
|
+
* ```typescript
|
|
3730
|
+
* // Step 1: Define all historical versions with $version discriminator
|
|
3731
|
+
* type TSerializedData_V1 = { name: string; $version: 1 };
|
|
3732
|
+
* type TSerializedData_V2 = { name: string; age: number; $version: 2 };
|
|
3733
|
+
* type TSerializedData_V3 = { name: string; age: number; email: string; $version: 3 };
|
|
3734
|
+
*
|
|
3735
|
+
* // Step 2: Create union type and current version type
|
|
3736
|
+
* type TSerializedData_Vstar = TSerializedData_V1 | TSerializedData_V2 | TSerializedData_V3;
|
|
3737
|
+
* type TSerializedData = TSerializedData_V3;
|
|
3738
|
+
*
|
|
3739
|
+
* // Step 3: Create upgrader with transition functions
|
|
3740
|
+
* const upgrader = DataUpgrader.create<TSerializedData_Vstar, TSerializedData>(3)
|
|
3741
|
+
* .addTransition(1, 2, async (data: TSerializedData_V1) => ({
|
|
3742
|
+
* ...data,
|
|
3743
|
+
* age: 0,
|
|
3744
|
+
* $version: 2
|
|
3745
|
+
* }))
|
|
3746
|
+
* .addTransition(2, 3, async (data: TSerializedData_V2) => ({
|
|
3747
|
+
* ...data,
|
|
3748
|
+
* email: "",
|
|
3749
|
+
* $version: 3
|
|
3750
|
+
* }))
|
|
3751
|
+
* .addTransition(1, 3, async (data: TSerializedData_V1) => ({
|
|
3752
|
+
* // Optional: Direct path for efficiency
|
|
3753
|
+
* ...data,
|
|
3754
|
+
* age: 0,
|
|
3755
|
+
* email: "",
|
|
3756
|
+
* $version: 3
|
|
3757
|
+
* }));
|
|
3758
|
+
*
|
|
3759
|
+
* // Step 4: Use in data loading - handles any version transparently
|
|
3760
|
+
* async function loadData(json: string): Promise<TSerializedData> {
|
|
3761
|
+
* const data = parseJson<TSerializedData_Vstar>(json);
|
|
3762
|
+
* return await upgrader.upgrade(data);
|
|
3763
|
+
* }
|
|
3764
|
+
*
|
|
3765
|
+
* // Works with any version:
|
|
3766
|
+
* const v1 = await loadData('{"name":"Alice","$version":1}'); // → V3
|
|
3767
|
+
* const v2 = await loadData('{"name":"Bob","age":25,"$version":2}'); // → V3
|
|
3768
|
+
* const v3 = await loadData('{"name":"Carol","age":30,"email":"c@example.com","$version":3}'); // → V3
|
|
3769
|
+
* ```
|
|
3770
|
+
*
|
|
3771
|
+
* @typeParam X - Union type of all possible data versions (must extend TUpgradable)
|
|
3772
|
+
* @typeParam XLatest - The latest version type (must extend X)
|
|
3773
|
+
*
|
|
3774
|
+
* @see {@link isUpgradable | Type guard for checking if data is upgradable}
|
|
3775
|
+
* @see {@link DataUpgrader.Errors | Error types for upgrade failures}
|
|
3776
|
+
*/
|
|
3694
3777
|
class DataUpgrader {
|
|
3695
3778
|
latestVersion;
|
|
3696
|
-
transitions
|
|
3697
|
-
constructor(latestVersion) {
|
|
3779
|
+
transitions;
|
|
3780
|
+
constructor(latestVersion, transitions = {}) {
|
|
3698
3781
|
this.latestVersion = latestVersion;
|
|
3782
|
+
this.transitions = transitions;
|
|
3699
3783
|
}
|
|
3700
3784
|
addTransition(from, to, apply) {
|
|
3701
3785
|
if (from === undefined || from < 0)
|
|
@@ -3742,6 +3826,17 @@ class DataUpgrader {
|
|
|
3742
3826
|
static create(latest) {
|
|
3743
3827
|
return new DataUpgrader(latest);
|
|
3744
3828
|
}
|
|
3829
|
+
/**
|
|
3830
|
+
* Creates a DataUpgrader from a DataUpgraderBuilder instance.
|
|
3831
|
+
* @internal
|
|
3832
|
+
*/
|
|
3833
|
+
static fromBuilder(builder, latestVersion) {
|
|
3834
|
+
// Extract transitions from builder and convert to strongly-typed TTransitionMatrix
|
|
3835
|
+
// This type assertion is safe because the builder has validated all transitions
|
|
3836
|
+
// at compile-time via its type parameters
|
|
3837
|
+
const transitions = builder.getTransitions();
|
|
3838
|
+
return new DataUpgrader(latestVersion, transitions);
|
|
3839
|
+
}
|
|
3745
3840
|
static Errors = {
|
|
3746
3841
|
EmptyUpgrade: EmptyUpgradeError,
|
|
3747
3842
|
UnavailableUpgrade: UnavailableUpgradeError
|
|
@@ -3750,9 +3845,146 @@ class DataUpgrader {
|
|
|
3750
3845
|
function isUpgradable(obj) {
|
|
3751
3846
|
return isDefined(obj) && typeof obj === "object" && VERSION_FIELD in obj && isNumber(obj.$version) && isPositiveNumber(obj.$version);
|
|
3752
3847
|
}
|
|
3848
|
+
/**
|
|
3849
|
+
* The current system is unable to catch the following problems at compile-time:
|
|
3850
|
+
* type A_V1 = { a: number, $version: 1 }
|
|
3851
|
+
* type A_V2 = { a: number, b: number, $version: 2 }
|
|
3852
|
+
* type A_V3 = { a: number, b: number, c: string, $version: 3 }
|
|
3853
|
+
* type A_Vstar = A_V1 | A_V2 | A_V3;
|
|
3854
|
+
* type A = A_V3;
|
|
3855
|
+
*
|
|
3856
|
+
* const DU1 = DataUpgrader.create<A_Vstar, A>( 3 );
|
|
3857
|
+
*
|
|
3858
|
+
* const DU2 = DataUpgrader.create<A_Vstar, A>( 3 )
|
|
3859
|
+
* .addTransition( 1, 3, x => ( { ...x, b: 2, c: "foo", $version: 3 } ) )
|
|
3860
|
+
* .addTransition( 1, 3, x => ( { ...x, b: 3, c: "bar", $version: 3 } ) )
|
|
3861
|
+
* ;
|
|
3862
|
+
*
|
|
3863
|
+
* const DU3 = DataUpgrader.create<A_Vstar, A>( 3 )
|
|
3864
|
+
* .addTransition( 1, 2, x => ( { ...x, b: 2, $version: 2 } ) )
|
|
3865
|
+
* .addTransition( 2, 1, x => ( { ...x, $version: 1 } ) )
|
|
3866
|
+
* ;
|
|
3867
|
+
*/
|
|
3868
|
+
|
|
3869
|
+
/**
|
|
3870
|
+
* Builder for creating type-safe DataUpgrader instances with compile-time safety.
|
|
3871
|
+
*
|
|
3872
|
+
* Prevents at compile-time:
|
|
3873
|
+
* - Missing upgrade paths (incomplete version coverage)
|
|
3874
|
+
* - Duplicate transition definitions
|
|
3875
|
+
* - Backward transitions (e.g., version 2 → 1)
|
|
3876
|
+
*
|
|
3877
|
+
* @example
|
|
3878
|
+
* ```typescript
|
|
3879
|
+
* // Step 1: Define all historical versions with $version discriminator
|
|
3880
|
+
* type V1 = { name: string; $version: 1 };
|
|
3881
|
+
* type V2 = { name: string; age: number; $version: 2 };
|
|
3882
|
+
* type V3 = { name: string; age: number; email: string; $version: 3 };
|
|
3883
|
+
*
|
|
3884
|
+
* // Step 2: Build the upgrader with type-safe transitions
|
|
3885
|
+
* const upgrader = DataUpgraderBuilder.start<V1>()
|
|
3886
|
+
* .addTransition<V2>(2, async (data) => ({
|
|
3887
|
+
* ...data,
|
|
3888
|
+
* age: 0,
|
|
3889
|
+
* $version: 2
|
|
3890
|
+
* }))
|
|
3891
|
+
* .addTransition<V3>(3, async (data) => ({
|
|
3892
|
+
* ...data,
|
|
3893
|
+
* email: "",
|
|
3894
|
+
* $version: 3
|
|
3895
|
+
* }))
|
|
3896
|
+
* .addShortcut<V1, V3>(1, 3, async (data) => ({
|
|
3897
|
+
* // Optional: Direct path for efficiency
|
|
3898
|
+
* ...data,
|
|
3899
|
+
* age: 0,
|
|
3900
|
+
* email: "",
|
|
3901
|
+
* $version: 3
|
|
3902
|
+
* }))
|
|
3903
|
+
* .build(3);
|
|
3904
|
+
*
|
|
3905
|
+
* // Step 3: Use the upgrader
|
|
3906
|
+
* async function loadData(json: string): Promise<V3> {
|
|
3907
|
+
* const data = JSON.parse(json);
|
|
3908
|
+
* return await upgrader.upgrade(data);
|
|
3909
|
+
* }
|
|
3910
|
+
*
|
|
3911
|
+
* // Works with any version:
|
|
3912
|
+
* const v1 = await loadData('{"name":"Alice","$version":1}'); // → V3
|
|
3913
|
+
* const v2 = await loadData('{"name":"Bob","age":25,"$version":2}'); // → V3
|
|
3914
|
+
* const v3 = await loadData('{"name":"Carol","age":30,"email":"c@example.com","$version":3}'); // → V3
|
|
3915
|
+
* ```
|
|
3916
|
+
*/
|
|
3917
|
+
class DataUpgraderBuilder {
|
|
3918
|
+
latestVersion;
|
|
3919
|
+
transitions;
|
|
3920
|
+
constructor(latestVersion, transitions = {}) {
|
|
3921
|
+
this.latestVersion = latestVersion;
|
|
3922
|
+
this.transitions = transitions;
|
|
3923
|
+
}
|
|
3924
|
+
/** Starts building a new DataUpgrader from the lowest version. */
|
|
3925
|
+
static start() {
|
|
3926
|
+
return new DataUpgraderBuilder(1);
|
|
3927
|
+
}
|
|
3928
|
+
/**
|
|
3929
|
+
* Adds a sequential transition from the current latest version to a new version.
|
|
3930
|
+
* Only the new version type needs to be specified.
|
|
3931
|
+
*
|
|
3932
|
+
* @example
|
|
3933
|
+
* ```typescript
|
|
3934
|
+
* builder.addTransition<V2>(2, async (d) => ({ ...d, extra: 0, $version: 2 }))
|
|
3935
|
+
* ```
|
|
3936
|
+
*/
|
|
3937
|
+
addTransition(toVersion, apply) {
|
|
3938
|
+
const newTransitions = {
|
|
3939
|
+
...this.transitions,
|
|
3940
|
+
[toVersion]: {
|
|
3941
|
+
...(this.transitions[toVersion] ?? {}),
|
|
3942
|
+
[this.latestVersion]: { from: this.latestVersion, to: toVersion, apply: async (d) => apply(d) }
|
|
3943
|
+
}
|
|
3944
|
+
};
|
|
3945
|
+
const builder = new DataUpgraderBuilder(toVersion, newTransitions);
|
|
3946
|
+
return builder;
|
|
3947
|
+
}
|
|
3948
|
+
/**
|
|
3949
|
+
* Adds a shortcut transition between arbitrary versions.
|
|
3950
|
+
* Both versions must already be in the accumulated union type.
|
|
3951
|
+
*
|
|
3952
|
+
* @example
|
|
3953
|
+
* ```typescript
|
|
3954
|
+
* builder.addShortcut<V1, V3>(1, 3, async (d) => ({ ...d, extra: 0, flag: false, $version: 3 }))
|
|
3955
|
+
* ```
|
|
3956
|
+
*/
|
|
3957
|
+
addShortcut(fromVersion, toVersion, apply) {
|
|
3958
|
+
const newTransitions = {
|
|
3959
|
+
...this.transitions,
|
|
3960
|
+
[toVersion]: {
|
|
3961
|
+
...(this.transitions[toVersion] ?? {}),
|
|
3962
|
+
[fromVersion]: { from: fromVersion, to: toVersion, apply: async (d) => apply(d) }
|
|
3963
|
+
}
|
|
3964
|
+
};
|
|
3965
|
+
const builder = new DataUpgraderBuilder(this.latestVersion, newTransitions);
|
|
3966
|
+
return builder;
|
|
3967
|
+
}
|
|
3968
|
+
/**
|
|
3969
|
+
* Builds the DataUpgrader with the current latest version.
|
|
3970
|
+
*
|
|
3971
|
+
* @example
|
|
3972
|
+
* ```typescript
|
|
3973
|
+
* const upgrader = builder.build(3);
|
|
3974
|
+
* ```
|
|
3975
|
+
*/
|
|
3976
|
+
build(latestVersion) {
|
|
3977
|
+
return DataUpgrader.fromBuilder(this, latestVersion);
|
|
3978
|
+
}
|
|
3979
|
+
/** @internal */
|
|
3980
|
+
getTransitions() {
|
|
3981
|
+
return this.transitions;
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3753
3984
|
|
|
3754
3985
|
exports.Cached = Cached;
|
|
3755
3986
|
exports.DataUpgrader = DataUpgrader;
|
|
3987
|
+
exports.DataUpgraderBuilder = DataUpgraderBuilder;
|
|
3756
3988
|
exports.Deferred = Deferred;
|
|
3757
3989
|
exports.DeferredCanceledError = DeferredCanceledError;
|
|
3758
3990
|
exports.ErrorCannotInstantiatePresentOptionalWithEmptyValue = ErrorCannotInstantiatePresentOptionalWithEmptyValue;
|