@flumens/utils 0.4.0 → 0.5.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.
@@ -0,0 +1,100 @@
1
+ /** ****************************************************************************
2
+ * Internal state migration functionality.
3
+ *
4
+ * Cases:
5
+ * - First run - no migrations needed, just set the version.
6
+ * - Versions match - no migrations needed, the state was already migrated.
7
+ * - Versions differ - run all migrations in sequence, starting from the first
8
+ * one that is greater than the current version.
9
+ * - If no migrations are found for the current version, just set the version.
10
+ *
11
+ * Each migration should be idempotent if possible, as it may be interrupted
12
+ * and re-run multiple times.
13
+ **************************************************************************** */
14
+ export type Migration = {
15
+ /** Semver version when this migration becomes relevant (inclusive). */
16
+ version: string;
17
+ /** Optional descriptive name for logging. */
18
+ name?: string;
19
+ /** The migration script. Keep it idempotent if possible. */
20
+ up: () => void | Promise<void>;
21
+ };
22
+ export default class MigrationsManager {
23
+ private migrations;
24
+ private newVersion;
25
+ private currentVersion;
26
+ private updateVersion;
27
+ /**
28
+ * Creates a new MigrationsManager instance.
29
+ *
30
+ * @param migrations - Array of migration objects to be applied
31
+ * @param newVersion - A function that returns the target version we want to migrate to
32
+ * @param currentVersion - A function that returns the last migrated version
33
+ * @param updateVersion - A function to persist the version after each migration
34
+ */
35
+ constructor(migrations: Migration[], newVersion: () => string | Promise<string>, currentVersion: () => (string | null) | Promise<string | null>, updateVersion: (version: string) => Promise<void>);
36
+ /**
37
+ * Main update function.
38
+ *
39
+ * Flow:
40
+ * 1. If currentVersion === newVersion -> No migrations needed
41
+ * 2. If currentVersion is null -> First run, just set version
42
+ * 3. If currentVersion < newVersion -> Find and run applicable migrations
43
+ * 4. Each migration updates the version individually for interruption safety
44
+ */
45
+ run(): Promise<void>;
46
+ /**
47
+ * Returns the index of the first found update in sequence.
48
+ *
49
+ * Finds the first migration whose version is greater than currentVersion.
50
+ * Since migrations are sorted, this is the starting point for running updates.
51
+ *
52
+ * Example:
53
+ * - currentVersion: "3.5.0"
54
+ * - migrations: ["2.0.0", "3.0.0", "4.0.0", "5.0.0"]
55
+ * - returns: index 2 (migration "4.0.0")
56
+ *
57
+ * @returns Index of first migration to run, or -1 if none found
58
+ */
59
+ private findFirstMigrationIndex;
60
+ /**
61
+ * Recursively apply all updates.
62
+ *
63
+ * Runs migrations sequentially starting from migrationIndex.
64
+ * After each successful migration, the version is persisted immediately.
65
+ *
66
+ * Why persist after each migration?
67
+ * - If the app crashes or migration is interrupted, we don't re-run
68
+ * already completed migrations on next startup
69
+ * - Each migration should be idempotent to handle this scenario
70
+ *
71
+ * @param migrationIndex - Index in migrations array to start from
72
+ * @throws Rethrows any error from migration.up() to halt the process
73
+ */
74
+ private applyUpdates;
75
+ /**
76
+ * https://gist.github.com/alexey-bass/1115557
77
+ * Simply compares left version to right one.
78
+ *
79
+ * Compares semantic version strings numerically, part by part.
80
+ * Handles versions with different segment counts (e.g., "1.2" vs "1.2.3").
81
+ *
82
+ * Examples:
83
+ * versionCompare('1.1', '1.2') => -1 (1.1 is lower)
84
+ * versionCompare('1.1', '1.1') => 0 (equal)
85
+ * versionCompare('1.2', '1.1') => 1 (1.2 is higher)
86
+ * versionCompare('2.23.3', '2.22.3') => 1
87
+ * versionCompare('1.0', '1.0.1') => -1 (missing segments treated as 0)
88
+ *
89
+ * Returns:
90
+ * -1 = left is LOWER than right
91
+ * 0 = they are equal (or invalid input)
92
+ * 1 = left is GREATER than right
93
+ *
94
+ * Note: Returns 0 for invalid input instead of throwing
95
+ *
96
+ * @param left - First version string (e.g., "1.2.3")
97
+ * @param right - Second version string (e.g., "1.2.4")
98
+ */
99
+ private static versionCompare;
100
+ }
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});const t=require("tslib");class i{constructor(t,e,r,s){this.migrations=t,this.newVersion=e,this.currentVersion=r,this.updateVersion=s;this.migrations.sort(((t,e)=>{const r=i.versionCompare(t.version,e.version);return 1===r?1:-1===r?-1:0}))}run(){return t.__awaiter(this,void 0,void 0,(function*(){const t=yield this.currentVersion(),i=yield this.newVersion();if(t!==i){if(!this.currentVersion)return console.log(`First run - setting version to ${i}`),void(yield this.updateVersion(i));const e=this.findFirstMigrationIndex(t);if(e<0)return;yield this.applyUpdates(e)}}))}findFirstMigrationIndex(t){if(!this.migrations.length)return-1;let e=-1;return this.migrations.some(((r,s)=>1===i.versionCompare(r.version,t)&&(e=s,!0))),e}applyUpdates(i){return t.__awaiter(this,void 0,void 0,(function*(){const t=this.migrations[i];try{console.log(`Update: ${t.version} ${t.name||""}`),yield t.up(),yield this.updateVersion(t.version)}catch(t){throw console.error(t),t}this.migrations.length-1<=i||(yield this.applyUpdates(i+1))}))}static versionCompare(t,i){if(typeof t+typeof i!="stringstring")return 0;const e=t.split("."),r=i.split("."),s=Math.max(e.length,r.length);for(let t=0;t<s;t++){if(e[t]&&!r[t]&&parseInt(e[t],10)>0||parseInt(e[t],10)>parseInt(r[t],10))return 1;if(r[t]&&!e[t]&&parseInt(r[t],10)>0||parseInt(e[t],10)<parseInt(r[t],10))return-1}return 0}}exports.default=i;
package/dist/index.d.ts CHANGED
@@ -9,3 +9,4 @@ export * from './sentry.js';
9
9
  export * from './errors.js';
10
10
  export * from './platform.js';
11
11
  export * from './uuid.js';
12
+ export * from './MigrationManager.js';
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.device=exports.string=void 0;const e=require("tslib");var r=require("./string.js");Object.defineProperty(exports,"string",{enumerable:!0,get:function(){return e.__importDefault(r).default}});var t=require("./device.js");Object.defineProperty(exports,"device",{enumerable:!0,get:function(){return e.__importDefault(t).default}}),e.__exportStar(require("./deferred.js"),exports),e.__exportStar(require("./date.js"),exports),e.__exportStar(require("./image.js"),exports),e.__exportStar(require("./location.js"),exports),e.__exportStar(require("./type.js"),exports),e.__exportStar(require("./sentry.js"),exports),e.__exportStar(require("./errors.js"),exports),e.__exportStar(require("./platform.js"),exports),e.__exportStar(require("./uuid.js"),exports);
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.device=exports.string=void 0;const e=require("tslib");var r=require("./string.js");Object.defineProperty(exports,"string",{enumerable:!0,get:function(){return e.__importDefault(r).default}});var t=require("./device.js");Object.defineProperty(exports,"device",{enumerable:!0,get:function(){return e.__importDefault(t).default}}),e.__exportStar(require("./deferred.js"),exports),e.__exportStar(require("./date.js"),exports),e.__exportStar(require("./image.js"),exports),e.__exportStar(require("./location.js"),exports),e.__exportStar(require("./type.js"),exports),e.__exportStar(require("./sentry.js"),exports),e.__exportStar(require("./errors.js"),exports),e.__exportStar(require("./platform.js"),exports),e.__exportStar(require("./uuid.js"),exports),e.__exportStar(require("./MigrationManager.js"),exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flumens/utils",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {