@hot-updater/postgres 0.12.7 → 0.13.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.
- package/dist/index.cjs +75 -89
- package/dist/index.js +75 -89
- package/dist/postgres.d.ts +2 -2
- package/package.json +4 -3
- package/sql/bundles.sql +2 -2
- package/sql/get_target_app_version_list.sql +21 -0
- package/sql/get_update_info.spec.ts +41 -10
- package/sql/get_update_info.sql +35 -34
- package/sql/semver_satisfies.spec.ts +0 -26
- package/sql/semver_satisfies.sql +0 -126
package/dist/index.cjs
CHANGED
|
@@ -26,103 +26,89 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
26
26
|
__webpack_require__.d(__webpack_exports__, {
|
|
27
27
|
postgres: ()=>postgres
|
|
28
28
|
});
|
|
29
|
+
const plugin_core_namespaceObject = require("@hot-updater/plugin-core");
|
|
29
30
|
const external_kysely_namespaceObject = require("kysely");
|
|
30
31
|
const external_pg_namespaceObject = require("pg");
|
|
31
|
-
const postgres = (config, hooks)=>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
32
|
+
const postgres = (config, hooks)=>{
|
|
33
|
+
const pool = new external_pg_namespaceObject.Pool(config);
|
|
34
|
+
const dialect = new external_kysely_namespaceObject.PostgresDialect({
|
|
35
|
+
pool
|
|
36
|
+
});
|
|
37
|
+
const db = new external_kysely_namespaceObject.Kysely({
|
|
38
|
+
dialect
|
|
39
|
+
});
|
|
40
|
+
return (0, plugin_core_namespaceObject.createDatabasePlugin)("postgres", {
|
|
41
|
+
async onUnmount () {
|
|
42
|
+
await db.destroy();
|
|
43
|
+
await pool.end();
|
|
44
|
+
},
|
|
45
|
+
async getBundleById (bundleId) {
|
|
46
|
+
const data = await db.selectFrom("bundles").selectAll().where("id", "=", bundleId).executeTakeFirst();
|
|
47
|
+
if (!data) return null;
|
|
48
|
+
return {
|
|
49
|
+
enabled: data.enabled,
|
|
50
|
+
shouldForceUpdate: data.should_force_update,
|
|
51
|
+
fileHash: data.file_hash,
|
|
52
|
+
gitCommitHash: data.git_commit_hash,
|
|
53
|
+
id: data.id,
|
|
54
|
+
message: data.message,
|
|
55
|
+
platform: data.platform,
|
|
56
|
+
targetAppVersion: data.target_app_version,
|
|
57
|
+
channel: data.channel
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
async getBundles (options) {
|
|
61
|
+
const { where, limit, offset = 0 } = options ?? {};
|
|
62
|
+
let query = db.selectFrom("bundles").orderBy("id", "desc");
|
|
63
|
+
if (where?.channel) query = query.where("channel", "=", where.channel);
|
|
64
|
+
if (where?.platform) query = query.where("platform", "=", where.platform);
|
|
65
|
+
if (limit) query = query.limit(limit);
|
|
66
|
+
if (offset) query = query.offset(offset);
|
|
67
|
+
const data = await query.selectAll().execute();
|
|
68
|
+
return data.map((bundle)=>({
|
|
69
|
+
enabled: bundle.enabled,
|
|
70
|
+
shouldForceUpdate: bundle.should_force_update,
|
|
71
|
+
fileHash: bundle.file_hash,
|
|
72
|
+
gitCommitHash: bundle.git_commit_hash,
|
|
73
|
+
id: bundle.id,
|
|
74
|
+
message: bundle.message,
|
|
75
|
+
platform: bundle.platform,
|
|
76
|
+
targetAppVersion: bundle.target_app_version,
|
|
77
|
+
channel: bundle.channel
|
|
78
|
+
}));
|
|
79
|
+
},
|
|
80
|
+
async getChannels () {
|
|
81
|
+
const data = await db.selectFrom("bundles").select("channel").groupBy("channel").execute();
|
|
82
|
+
return data.map((bundle)=>bundle.channel);
|
|
83
|
+
},
|
|
84
|
+
async commitBundle ({ changedSets }) {
|
|
85
|
+
if (0 === changedSets.length) return;
|
|
86
|
+
const bundles = changedSets.map((op)=>op.data);
|
|
87
|
+
await db.transaction().execute(async (tx)=>{
|
|
88
|
+
for (const bundle of bundles)await tx.insertInto("bundles").values({
|
|
89
|
+
id: bundle.id,
|
|
90
|
+
enabled: bundle.enabled,
|
|
91
|
+
should_force_update: bundle.shouldForceUpdate,
|
|
92
|
+
file_hash: bundle.fileHash,
|
|
93
|
+
git_commit_hash: bundle.gitCommitHash,
|
|
94
|
+
message: bundle.message,
|
|
95
|
+
platform: bundle.platform,
|
|
96
|
+
target_app_version: bundle.targetAppVersion,
|
|
97
|
+
channel: bundle.channel
|
|
98
|
+
}).onConflict((oc)=>oc.column("id").doUpdateSet({
|
|
60
99
|
enabled: bundle.enabled,
|
|
61
|
-
file_url: bundle.fileUrl,
|
|
62
100
|
should_force_update: bundle.shouldForceUpdate,
|
|
63
101
|
file_hash: bundle.fileHash,
|
|
64
102
|
git_commit_hash: bundle.gitCommitHash,
|
|
65
103
|
message: bundle.message,
|
|
66
104
|
platform: bundle.platform,
|
|
67
|
-
target_app_version: bundle.targetAppVersion
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
message: bundle.message,
|
|
75
|
-
platform: bundle.platform,
|
|
76
|
-
target_app_version: bundle.targetAppVersion
|
|
77
|
-
})).execute();
|
|
78
|
-
});
|
|
79
|
-
changedIds.clear();
|
|
80
|
-
hooks?.onDatabaseUpdated?.();
|
|
81
|
-
},
|
|
82
|
-
async updateBundle (targetBundleId, newBundle) {
|
|
83
|
-
bundles = await this.getBundles();
|
|
84
|
-
const targetIndex = bundles.findIndex((u)=>u.id === targetBundleId);
|
|
85
|
-
if (-1 === targetIndex) throw new Error("target bundle version not found");
|
|
86
|
-
Object.assign(bundles[targetIndex], newBundle);
|
|
87
|
-
markChanged(targetBundleId);
|
|
88
|
-
},
|
|
89
|
-
async appendBundle (inputBundle) {
|
|
90
|
-
bundles = await this.getBundles();
|
|
91
|
-
bundles.unshift(inputBundle);
|
|
92
|
-
markChanged(inputBundle.id);
|
|
93
|
-
},
|
|
94
|
-
async getBundleById (bundleId) {
|
|
95
|
-
const data = await db.selectFrom("bundles").selectAll().where("id", "=", bundleId).executeTakeFirst();
|
|
96
|
-
if (!data) return null;
|
|
97
|
-
return {
|
|
98
|
-
enabled: data.enabled,
|
|
99
|
-
fileUrl: data.file_url,
|
|
100
|
-
shouldForceUpdate: data.should_force_update,
|
|
101
|
-
fileHash: data.file_hash,
|
|
102
|
-
gitCommitHash: data.git_commit_hash,
|
|
103
|
-
id: data.id,
|
|
104
|
-
message: data.message,
|
|
105
|
-
platform: data.platform,
|
|
106
|
-
targetAppVersion: data.target_app_version
|
|
107
|
-
};
|
|
108
|
-
},
|
|
109
|
-
async getBundles (refresh = false) {
|
|
110
|
-
if (bundles.length > 0 && !refresh) return bundles;
|
|
111
|
-
const data = await db.selectFrom("bundles").orderBy("id", "desc").selectAll().execute();
|
|
112
|
-
return data.map((bundle)=>({
|
|
113
|
-
enabled: bundle.enabled,
|
|
114
|
-
fileUrl: bundle.file_url,
|
|
115
|
-
shouldForceUpdate: bundle.should_force_update,
|
|
116
|
-
fileHash: bundle.file_hash,
|
|
117
|
-
gitCommitHash: bundle.git_commit_hash,
|
|
118
|
-
id: bundle.id,
|
|
119
|
-
message: bundle.message,
|
|
120
|
-
platform: bundle.platform,
|
|
121
|
-
targetAppVersion: bundle.target_app_version
|
|
122
|
-
}));
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
};
|
|
105
|
+
target_app_version: bundle.targetAppVersion,
|
|
106
|
+
channel: bundle.channel
|
|
107
|
+
})).execute();
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}, hooks);
|
|
111
|
+
};
|
|
126
112
|
var __webpack_export_target__ = exports;
|
|
127
113
|
for(var __webpack_i__ in __webpack_exports__)__webpack_export_target__[__webpack_i__] = __webpack_exports__[__webpack_i__];
|
|
128
114
|
if (__webpack_exports__.__esModule) Object.defineProperty(__webpack_export_target__, '__esModule', {
|
package/dist/index.js
CHANGED
|
@@ -1,98 +1,84 @@
|
|
|
1
|
+
import * as __WEBPACK_EXTERNAL_MODULE__hot_updater_plugin_core_40c1c502__ from "@hot-updater/plugin-core";
|
|
1
2
|
import * as __WEBPACK_EXTERNAL_MODULE_kysely__ from "kysely";
|
|
2
3
|
import * as __WEBPACK_EXTERNAL_MODULE_pg__ from "pg";
|
|
3
|
-
const postgres = (config, hooks)=>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
4
|
+
const postgres = (config, hooks)=>{
|
|
5
|
+
const pool = new __WEBPACK_EXTERNAL_MODULE_pg__.Pool(config);
|
|
6
|
+
const dialect = new __WEBPACK_EXTERNAL_MODULE_kysely__.PostgresDialect({
|
|
7
|
+
pool
|
|
8
|
+
});
|
|
9
|
+
const db = new __WEBPACK_EXTERNAL_MODULE_kysely__.Kysely({
|
|
10
|
+
dialect
|
|
11
|
+
});
|
|
12
|
+
return (0, __WEBPACK_EXTERNAL_MODULE__hot_updater_plugin_core_40c1c502__.createDatabasePlugin)("postgres", {
|
|
13
|
+
async onUnmount () {
|
|
14
|
+
await db.destroy();
|
|
15
|
+
await pool.end();
|
|
16
|
+
},
|
|
17
|
+
async getBundleById (bundleId) {
|
|
18
|
+
const data = await db.selectFrom("bundles").selectAll().where("id", "=", bundleId).executeTakeFirst();
|
|
19
|
+
if (!data) return null;
|
|
20
|
+
return {
|
|
21
|
+
enabled: data.enabled,
|
|
22
|
+
shouldForceUpdate: data.should_force_update,
|
|
23
|
+
fileHash: data.file_hash,
|
|
24
|
+
gitCommitHash: data.git_commit_hash,
|
|
25
|
+
id: data.id,
|
|
26
|
+
message: data.message,
|
|
27
|
+
platform: data.platform,
|
|
28
|
+
targetAppVersion: data.target_app_version,
|
|
29
|
+
channel: data.channel
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
async getBundles (options) {
|
|
33
|
+
const { where, limit, offset = 0 } = options ?? {};
|
|
34
|
+
let query = db.selectFrom("bundles").orderBy("id", "desc");
|
|
35
|
+
if (where?.channel) query = query.where("channel", "=", where.channel);
|
|
36
|
+
if (where?.platform) query = query.where("platform", "=", where.platform);
|
|
37
|
+
if (limit) query = query.limit(limit);
|
|
38
|
+
if (offset) query = query.offset(offset);
|
|
39
|
+
const data = await query.selectAll().execute();
|
|
40
|
+
return data.map((bundle)=>({
|
|
41
|
+
enabled: bundle.enabled,
|
|
42
|
+
shouldForceUpdate: bundle.should_force_update,
|
|
43
|
+
fileHash: bundle.file_hash,
|
|
44
|
+
gitCommitHash: bundle.git_commit_hash,
|
|
45
|
+
id: bundle.id,
|
|
46
|
+
message: bundle.message,
|
|
47
|
+
platform: bundle.platform,
|
|
48
|
+
targetAppVersion: bundle.target_app_version,
|
|
49
|
+
channel: bundle.channel
|
|
50
|
+
}));
|
|
51
|
+
},
|
|
52
|
+
async getChannels () {
|
|
53
|
+
const data = await db.selectFrom("bundles").select("channel").groupBy("channel").execute();
|
|
54
|
+
return data.map((bundle)=>bundle.channel);
|
|
55
|
+
},
|
|
56
|
+
async commitBundle ({ changedSets }) {
|
|
57
|
+
if (0 === changedSets.length) return;
|
|
58
|
+
const bundles = changedSets.map((op)=>op.data);
|
|
59
|
+
await db.transaction().execute(async (tx)=>{
|
|
60
|
+
for (const bundle of bundles)await tx.insertInto("bundles").values({
|
|
61
|
+
id: bundle.id,
|
|
62
|
+
enabled: bundle.enabled,
|
|
63
|
+
should_force_update: bundle.shouldForceUpdate,
|
|
64
|
+
file_hash: bundle.fileHash,
|
|
65
|
+
git_commit_hash: bundle.gitCommitHash,
|
|
66
|
+
message: bundle.message,
|
|
67
|
+
platform: bundle.platform,
|
|
68
|
+
target_app_version: bundle.targetAppVersion,
|
|
69
|
+
channel: bundle.channel
|
|
70
|
+
}).onConflict((oc)=>oc.column("id").doUpdateSet({
|
|
32
71
|
enabled: bundle.enabled,
|
|
33
|
-
file_url: bundle.fileUrl,
|
|
34
72
|
should_force_update: bundle.shouldForceUpdate,
|
|
35
73
|
file_hash: bundle.fileHash,
|
|
36
74
|
git_commit_hash: bundle.gitCommitHash,
|
|
37
75
|
message: bundle.message,
|
|
38
76
|
platform: bundle.platform,
|
|
39
|
-
target_app_version: bundle.targetAppVersion
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
message: bundle.message,
|
|
47
|
-
platform: bundle.platform,
|
|
48
|
-
target_app_version: bundle.targetAppVersion
|
|
49
|
-
})).execute();
|
|
50
|
-
});
|
|
51
|
-
changedIds.clear();
|
|
52
|
-
hooks?.onDatabaseUpdated?.();
|
|
53
|
-
},
|
|
54
|
-
async updateBundle (targetBundleId, newBundle) {
|
|
55
|
-
bundles = await this.getBundles();
|
|
56
|
-
const targetIndex = bundles.findIndex((u)=>u.id === targetBundleId);
|
|
57
|
-
if (-1 === targetIndex) throw new Error("target bundle version not found");
|
|
58
|
-
Object.assign(bundles[targetIndex], newBundle);
|
|
59
|
-
markChanged(targetBundleId);
|
|
60
|
-
},
|
|
61
|
-
async appendBundle (inputBundle) {
|
|
62
|
-
bundles = await this.getBundles();
|
|
63
|
-
bundles.unshift(inputBundle);
|
|
64
|
-
markChanged(inputBundle.id);
|
|
65
|
-
},
|
|
66
|
-
async getBundleById (bundleId) {
|
|
67
|
-
const data = await db.selectFrom("bundles").selectAll().where("id", "=", bundleId).executeTakeFirst();
|
|
68
|
-
if (!data) return null;
|
|
69
|
-
return {
|
|
70
|
-
enabled: data.enabled,
|
|
71
|
-
fileUrl: data.file_url,
|
|
72
|
-
shouldForceUpdate: data.should_force_update,
|
|
73
|
-
fileHash: data.file_hash,
|
|
74
|
-
gitCommitHash: data.git_commit_hash,
|
|
75
|
-
id: data.id,
|
|
76
|
-
message: data.message,
|
|
77
|
-
platform: data.platform,
|
|
78
|
-
targetAppVersion: data.target_app_version
|
|
79
|
-
};
|
|
80
|
-
},
|
|
81
|
-
async getBundles (refresh = false) {
|
|
82
|
-
if (bundles.length > 0 && !refresh) return bundles;
|
|
83
|
-
const data = await db.selectFrom("bundles").orderBy("id", "desc").selectAll().execute();
|
|
84
|
-
return data.map((bundle)=>({
|
|
85
|
-
enabled: bundle.enabled,
|
|
86
|
-
fileUrl: bundle.file_url,
|
|
87
|
-
shouldForceUpdate: bundle.should_force_update,
|
|
88
|
-
fileHash: bundle.file_hash,
|
|
89
|
-
gitCommitHash: bundle.git_commit_hash,
|
|
90
|
-
id: bundle.id,
|
|
91
|
-
message: bundle.message,
|
|
92
|
-
platform: bundle.platform,
|
|
93
|
-
targetAppVersion: bundle.target_app_version
|
|
94
|
-
}));
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
};
|
|
77
|
+
target_app_version: bundle.targetAppVersion,
|
|
78
|
+
channel: bundle.channel
|
|
79
|
+
})).execute();
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}, hooks);
|
|
83
|
+
};
|
|
98
84
|
export { postgres };
|
package/dist/postgres.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DatabasePluginHooks } from "@hot-updater/plugin-core";
|
|
2
2
|
import { type PoolConfig } from "pg";
|
|
3
3
|
export interface PostgresConfig extends PoolConfig {
|
|
4
4
|
}
|
|
5
|
-
export declare const postgres: (config: PostgresConfig, hooks?: DatabasePluginHooks) => (
|
|
5
|
+
export declare const postgres: (config: PostgresConfig, hooks?: DatabasePluginHooks) => (options: import("@hot-updater/plugin-core").BasePluginArgs) => import("@hot-updater/plugin-core").DatabasePlugin;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/postgres",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.13.0",
|
|
5
5
|
"description": "React Native OTA solution for self-hosted",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.js",
|
|
@@ -29,13 +29,14 @@
|
|
|
29
29
|
"package.json"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@hot-updater/core": "0.
|
|
33
|
-
"@hot-updater/plugin-core": "0.
|
|
32
|
+
"@hot-updater/core": "0.13.0",
|
|
33
|
+
"@hot-updater/plugin-core": "0.13.0",
|
|
34
34
|
"kysely": "^0.27.5",
|
|
35
35
|
"pg": "^8.13.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@electric-sql/pglite": "^0.2.15",
|
|
39
|
+
"@hot-updater/js": "0.13.0",
|
|
39
40
|
"@types/pg": "^8.11.10",
|
|
40
41
|
"camelcase-keys": "^9.1.3"
|
|
41
42
|
},
|
package/sql/bundles.sql
CHANGED
|
@@ -8,10 +8,10 @@ CREATE TABLE bundles (
|
|
|
8
8
|
target_app_version text NOT NULL,
|
|
9
9
|
should_force_update boolean NOT NULL,
|
|
10
10
|
enabled boolean NOT NULL,
|
|
11
|
-
file_url text NOT NULL,
|
|
12
11
|
file_hash text NOT NULL,
|
|
13
12
|
git_commit_hash text,
|
|
14
|
-
message text
|
|
13
|
+
message text,
|
|
14
|
+
channel text NOT NULL DEFAULT 'production'
|
|
15
15
|
);
|
|
16
16
|
|
|
17
17
|
CREATE INDEX bundles_target_app_version_idx ON bundles(target_app_version);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-- HotUpdater.get_target_app_version_list
|
|
2
|
+
|
|
3
|
+
CREATE OR REPLACE FUNCTION get_target_app_version_list (
|
|
4
|
+
app_platform platforms,
|
|
5
|
+
min_bundle_id uuid
|
|
6
|
+
)
|
|
7
|
+
RETURNS TABLE (
|
|
8
|
+
target_app_version text
|
|
9
|
+
)
|
|
10
|
+
LANGUAGE plpgsql
|
|
11
|
+
AS
|
|
12
|
+
$$
|
|
13
|
+
BEGIN
|
|
14
|
+
RETURN QUERY
|
|
15
|
+
SELECT b.target_app_version
|
|
16
|
+
FROM bundles b
|
|
17
|
+
WHERE b.platform = app_platform
|
|
18
|
+
AND b.id >= min_bundle_id
|
|
19
|
+
GROUP BY b.target_app_version;
|
|
20
|
+
END;
|
|
21
|
+
$$;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { PGlite } from "@electric-sql/pglite";
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
type Bundle,
|
|
4
|
+
type GetBundlesArgs,
|
|
5
|
+
NIL_UUID,
|
|
6
|
+
type UpdateInfo,
|
|
7
|
+
} from "@hot-updater/core";
|
|
3
8
|
import { setupGetUpdateInfoTestSuite } from "@hot-updater/core/test-utils";
|
|
9
|
+
import { filterCompatibleAppVersions } from "@hot-updater/js";
|
|
4
10
|
import camelcaseKeys from "camelcase-keys";
|
|
5
11
|
import { afterAll, beforeEach, describe } from "vitest";
|
|
6
12
|
import { prepareSql } from "./prepareSql";
|
|
@@ -8,18 +14,18 @@ import { prepareSql } from "./prepareSql";
|
|
|
8
14
|
const createInsertBundleQuery = (bundle: Bundle) => {
|
|
9
15
|
return `
|
|
10
16
|
INSERT INTO bundles (
|
|
11
|
-
id,
|
|
12
|
-
should_force_update, enabled, git_commit_hash, message
|
|
17
|
+
id, file_hash, platform, target_app_version,
|
|
18
|
+
should_force_update, enabled, git_commit_hash, message, channel
|
|
13
19
|
) VALUES (
|
|
14
20
|
'${bundle.id}',
|
|
15
|
-
'${bundle.fileUrl}',
|
|
16
21
|
'${bundle.fileHash}',
|
|
17
22
|
'${bundle.platform}',
|
|
18
23
|
'${bundle.targetAppVersion}',
|
|
19
24
|
${bundle.shouldForceUpdate},
|
|
20
25
|
${bundle.enabled},
|
|
21
26
|
${bundle.gitCommitHash ? `'${bundle.gitCommitHash}'` : "null"},
|
|
22
|
-
${bundle.message ? `'${bundle.message}'` : "null"}
|
|
27
|
+
${bundle.message ? `'${bundle.message}'` : "null"},
|
|
28
|
+
'${bundle.channel}'
|
|
23
29
|
);
|
|
24
30
|
`;
|
|
25
31
|
};
|
|
@@ -28,20 +34,45 @@ const createGetUpdateInfo =
|
|
|
28
34
|
(db: PGlite) =>
|
|
29
35
|
async (
|
|
30
36
|
bundles: Bundle[],
|
|
31
|
-
{
|
|
37
|
+
{
|
|
38
|
+
appVersion,
|
|
39
|
+
bundleId,
|
|
40
|
+
platform,
|
|
41
|
+
minBundleId = NIL_UUID,
|
|
42
|
+
channel = "production",
|
|
43
|
+
}: GetBundlesArgs,
|
|
32
44
|
): Promise<UpdateInfo | null> => {
|
|
33
45
|
await db.exec(createInsertBundleQuerys(bundles));
|
|
34
46
|
|
|
47
|
+
const { rows: appVersionList } = await db.query<{
|
|
48
|
+
target_app_version: string;
|
|
49
|
+
}>(
|
|
50
|
+
`
|
|
51
|
+
SELECT target_app_version FROM get_target_app_version_list('${platform}', '${minBundleId}');
|
|
52
|
+
`,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const targetAppVersionList = filterCompatibleAppVersions(
|
|
56
|
+
appVersionList?.map((group) => group.target_app_version) ?? [],
|
|
57
|
+
appVersion,
|
|
58
|
+
);
|
|
59
|
+
|
|
35
60
|
const result = await db.query<{
|
|
36
61
|
id: string;
|
|
37
62
|
should_force_update: boolean;
|
|
38
|
-
|
|
39
|
-
file_hash: string;
|
|
63
|
+
message: string;
|
|
40
64
|
status: string;
|
|
41
65
|
}>(
|
|
42
66
|
`
|
|
43
|
-
SELECT * FROM get_update_info(
|
|
44
|
-
|
|
67
|
+
SELECT * FROM get_update_info(
|
|
68
|
+
'${platform}',
|
|
69
|
+
'${appVersion}',
|
|
70
|
+
'${bundleId}',
|
|
71
|
+
'${minBundleId ?? NIL_UUID}',
|
|
72
|
+
'${channel}',
|
|
73
|
+
ARRAY[${targetAppVersionList.map((v) => `'${v}'`).join(",")}]::text[]
|
|
74
|
+
);
|
|
75
|
+
`,
|
|
45
76
|
);
|
|
46
77
|
|
|
47
78
|
return result.rows[0]
|
package/sql/get_update_info.sql
CHANGED
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
CREATE OR REPLACE FUNCTION get_update_info (
|
|
4
4
|
app_platform platforms,
|
|
5
5
|
app_version text,
|
|
6
|
-
bundle_id uuid
|
|
6
|
+
bundle_id uuid,
|
|
7
|
+
min_bundle_id uuid,
|
|
8
|
+
target_channel text,
|
|
9
|
+
target_app_version_list text[]
|
|
7
10
|
)
|
|
8
11
|
RETURNS TABLE (
|
|
9
12
|
id uuid,
|
|
10
13
|
should_force_update boolean,
|
|
11
|
-
|
|
12
|
-
file_hash text,
|
|
14
|
+
message text,
|
|
13
15
|
status text
|
|
14
16
|
)
|
|
15
17
|
LANGUAGE plpgsql
|
|
@@ -19,63 +21,62 @@ DECLARE
|
|
|
19
21
|
NIL_UUID CONSTANT uuid := '00000000-0000-0000-0000-000000000000';
|
|
20
22
|
BEGIN
|
|
21
23
|
RETURN QUERY
|
|
22
|
-
WITH
|
|
24
|
+
WITH update_candidate AS (
|
|
23
25
|
SELECT
|
|
24
26
|
b.id,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
b.file_hash,
|
|
29
|
-
'ROLLBACK' AS status
|
|
27
|
+
b.should_force_update,
|
|
28
|
+
b.message,
|
|
29
|
+
'UPDATE' AS status
|
|
30
30
|
FROM bundles b
|
|
31
31
|
WHERE b.enabled = TRUE
|
|
32
32
|
AND b.platform = app_platform
|
|
33
|
-
AND b.id
|
|
33
|
+
AND b.id >= bundle_id
|
|
34
|
+
AND b.id > min_bundle_id
|
|
35
|
+
AND b.target_app_version IN (SELECT unnest(target_app_version_list))
|
|
36
|
+
AND b.channel = target_channel
|
|
34
37
|
ORDER BY b.id DESC
|
|
35
38
|
LIMIT 1
|
|
36
39
|
),
|
|
37
|
-
|
|
40
|
+
rollback_candidate AS (
|
|
38
41
|
SELECT
|
|
39
42
|
b.id,
|
|
40
|
-
|
|
41
|
-
b.
|
|
42
|
-
|
|
43
|
-
'UPDATE' AS status
|
|
43
|
+
TRUE AS should_force_update,
|
|
44
|
+
b.message,
|
|
45
|
+
'ROLLBACK' AS status
|
|
44
46
|
FROM bundles b
|
|
45
47
|
WHERE b.enabled = TRUE
|
|
46
48
|
AND b.platform = app_platform
|
|
47
|
-
AND b.id
|
|
48
|
-
AND
|
|
49
|
+
AND b.id < bundle_id
|
|
50
|
+
AND b.id > min_bundle_id
|
|
49
51
|
ORDER BY b.id DESC
|
|
50
52
|
LIMIT 1
|
|
51
53
|
),
|
|
52
54
|
final_result AS (
|
|
53
|
-
SELECT *
|
|
54
|
-
FROM update_candidate
|
|
55
|
-
|
|
55
|
+
SELECT * FROM update_candidate
|
|
56
56
|
UNION ALL
|
|
57
|
-
|
|
58
|
-
SELECT *
|
|
59
|
-
FROM rollback_candidate
|
|
57
|
+
SELECT * FROM rollback_candidate
|
|
60
58
|
WHERE NOT EXISTS (SELECT 1 FROM update_candidate)
|
|
61
59
|
)
|
|
62
60
|
SELECT *
|
|
63
|
-
FROM final_result
|
|
61
|
+
FROM final_result
|
|
62
|
+
WHERE final_result.id != bundle_id
|
|
64
63
|
|
|
65
64
|
UNION ALL
|
|
66
|
-
|
|
67
|
-
When there are no final results and bundle_id != NIL_UUID,
|
|
68
|
-
add one fallback row.
|
|
69
|
-
This fallback row is also ROLLBACK so shouldForceUpdate = TRUE.
|
|
70
|
-
*/
|
|
65
|
+
|
|
71
66
|
SELECT
|
|
72
67
|
NIL_UUID AS id,
|
|
73
|
-
TRUE AS should_force_update,
|
|
74
|
-
NULL AS
|
|
75
|
-
NULL AS file_hash,
|
|
68
|
+
TRUE AS should_force_update,
|
|
69
|
+
NULL AS message,
|
|
76
70
|
'ROLLBACK' AS status
|
|
77
71
|
WHERE (SELECT COUNT(*) FROM final_result) = 0
|
|
78
|
-
AND bundle_id != NIL_UUID
|
|
79
|
-
|
|
72
|
+
AND bundle_id != NIL_UUID
|
|
73
|
+
AND bundle_id > min_bundle_id
|
|
74
|
+
AND NOT EXISTS (
|
|
75
|
+
SELECT 1
|
|
76
|
+
FROM bundles b
|
|
77
|
+
WHERE b.id = bundle_id
|
|
78
|
+
AND b.enabled = TRUE
|
|
79
|
+
AND b.platform = app_platform
|
|
80
|
+
);
|
|
80
81
|
END;
|
|
81
82
|
$$;
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { PGlite } from "@electric-sql/pglite";
|
|
2
|
-
import { setupSemverSatisfiesTestSuite } from "@hot-updater/core/test-utils";
|
|
3
|
-
import { afterAll, describe } from "vitest";
|
|
4
|
-
import { prepareSql } from "./prepareSql";
|
|
5
|
-
|
|
6
|
-
const db = new PGlite();
|
|
7
|
-
const sql = await prepareSql();
|
|
8
|
-
await db.exec(sql);
|
|
9
|
-
|
|
10
|
-
const createSemverSatisfies =
|
|
11
|
-
(db: PGlite) => async (targetAppVersion: string, currentVersion: string) => {
|
|
12
|
-
const result = await db.query<{ actual: boolean }>(`
|
|
13
|
-
SELECT semver_satisfies('${targetAppVersion}', '${currentVersion}') AS actual;
|
|
14
|
-
`);
|
|
15
|
-
return result.rows[0].actual;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const semverSatisfies = createSemverSatisfies(db);
|
|
19
|
-
|
|
20
|
-
describe("semverSatisfies", () => {
|
|
21
|
-
afterAll(async () => {
|
|
22
|
-
await db.close();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
setupSemverSatisfiesTestSuite({ semverSatisfies });
|
|
26
|
-
});
|
package/sql/semver_satisfies.sql
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
-- HotUpdater.semver_satisfies
|
|
2
|
-
|
|
3
|
-
CREATE OR REPLACE FUNCTION semver_satisfies(range_expression TEXT, version TEXT)
|
|
4
|
-
RETURNS BOOLEAN AS $$
|
|
5
|
-
DECLARE
|
|
6
|
-
version_parts TEXT[];
|
|
7
|
-
version_major INT;
|
|
8
|
-
version_minor INT;
|
|
9
|
-
version_patch INT;
|
|
10
|
-
satisfies BOOLEAN := FALSE;
|
|
11
|
-
BEGIN
|
|
12
|
-
-- Split the version into major, minor, and patch
|
|
13
|
-
version_parts := string_to_array(version, '.');
|
|
14
|
-
version_major := version_parts[1]::INT;
|
|
15
|
-
version_minor := version_parts[2]::INT;
|
|
16
|
-
version_patch := version_parts[3]::INT;
|
|
17
|
-
|
|
18
|
-
-- Parse range expression and evaluate
|
|
19
|
-
IF range_expression ~ '^\d+\.\d+\.\d+$' THEN
|
|
20
|
-
-- Exact match
|
|
21
|
-
satisfies := (range_expression = version);
|
|
22
|
-
|
|
23
|
-
ELSIF range_expression = '*' THEN
|
|
24
|
-
-- Matches any version
|
|
25
|
-
satisfies := TRUE;
|
|
26
|
-
|
|
27
|
-
ELSIF range_expression ~ '^\d+\.x\.x$' THEN
|
|
28
|
-
-- Matches major.x.x
|
|
29
|
-
DECLARE
|
|
30
|
-
major_range INT := split_part(range_expression, '.', 1)::INT;
|
|
31
|
-
BEGIN
|
|
32
|
-
satisfies := (version_major = major_range);
|
|
33
|
-
END;
|
|
34
|
-
|
|
35
|
-
ELSIF range_expression ~ '^\d+\.\d+\.x$' THEN
|
|
36
|
-
-- Matches major.minor.x
|
|
37
|
-
DECLARE
|
|
38
|
-
major_range INT := split_part(range_expression, '.', 1)::INT;
|
|
39
|
-
minor_range INT := split_part(range_expression, '.', 2)::INT;
|
|
40
|
-
BEGIN
|
|
41
|
-
satisfies := (version_major = major_range AND version_minor = minor_range);
|
|
42
|
-
END;
|
|
43
|
-
|
|
44
|
-
ELSIF range_expression ~ '^\d+\.\d+$' THEN
|
|
45
|
-
-- Matches major.minor
|
|
46
|
-
DECLARE
|
|
47
|
-
major_range INT := split_part(range_expression, '.', 1)::INT;
|
|
48
|
-
minor_range INT := split_part(range_expression, '.', 2)::INT;
|
|
49
|
-
BEGIN
|
|
50
|
-
satisfies := (version_major = major_range AND version_minor = minor_range);
|
|
51
|
-
END;
|
|
52
|
-
|
|
53
|
-
ELSIF range_expression ~ '^\d+\.\d+\.\d+ - \d+\.\d+\.\d+$' THEN
|
|
54
|
-
-- Matches range e.g., 1.2.3 - 1.2.7
|
|
55
|
-
DECLARE
|
|
56
|
-
lower_bound TEXT := split_part(range_expression, ' - ', 1);
|
|
57
|
-
upper_bound TEXT := split_part(range_expression, ' - ', 2);
|
|
58
|
-
BEGIN
|
|
59
|
-
satisfies := (version >= lower_bound AND version <= upper_bound);
|
|
60
|
-
END;
|
|
61
|
-
|
|
62
|
-
ELSIF range_expression ~ '^>=\d+\.\d+\.\d+ <\d+\.\d+\.\d+$' THEN
|
|
63
|
-
-- Matches range with inequalities
|
|
64
|
-
DECLARE
|
|
65
|
-
lower_bound TEXT := regexp_replace(range_expression, '>=([\d\.]+) <.*', '\1');
|
|
66
|
-
upper_bound TEXT := regexp_replace(range_expression, '.*<([\d\.]+)', '\1');
|
|
67
|
-
BEGIN
|
|
68
|
-
satisfies := (version >= lower_bound AND version < upper_bound);
|
|
69
|
-
END;
|
|
70
|
-
|
|
71
|
-
ELSIF range_expression ~ '^~\d+\.\d+\.\d+$' THEN
|
|
72
|
-
-- Matches ~1.2.3 (>=1.2.3 <1.3.0)
|
|
73
|
-
DECLARE
|
|
74
|
-
lower_bound TEXT := regexp_replace(range_expression, '~', '');
|
|
75
|
-
upper_bound_major INT := split_part(lower_bound, '.', 1)::INT;
|
|
76
|
-
upper_bound_minor INT := split_part(lower_bound, '.', 2)::INT + 1;
|
|
77
|
-
upper_bound TEXT := upper_bound_major || '.' || upper_bound_minor || '.0';
|
|
78
|
-
BEGIN
|
|
79
|
-
satisfies := (version >= lower_bound AND version < upper_bound);
|
|
80
|
-
END;
|
|
81
|
-
|
|
82
|
-
ELSIF range_expression ~ '^\^\d+\.\d+\.\d+$' THEN
|
|
83
|
-
-- Matches ^1.2.3 (>=1.2.3 <2.0.0)
|
|
84
|
-
DECLARE
|
|
85
|
-
lower_bound TEXT := regexp_replace(range_expression, '\^', '');
|
|
86
|
-
upper_bound_major INT := split_part(lower_bound, '.', 1)::INT + 1;
|
|
87
|
-
upper_bound TEXT := upper_bound_major || '.0.0';
|
|
88
|
-
BEGIN
|
|
89
|
-
satisfies := (version >= lower_bound AND version < upper_bound);
|
|
90
|
-
END;
|
|
91
|
-
|
|
92
|
-
-- [Added] 1) Single major version pattern '^(\d+)$'
|
|
93
|
-
ELSIF range_expression ~ '^\d+$' THEN
|
|
94
|
-
/*
|
|
95
|
-
e.g.) "1" is interpreted as (>=1.0.0 <2.0.0) in semver range
|
|
96
|
-
"2" would be interpreted as (>=2.0.0 <3.0.0)
|
|
97
|
-
*/
|
|
98
|
-
DECLARE
|
|
99
|
-
major_range INT := range_expression::INT;
|
|
100
|
-
lower_bound TEXT := major_range || '.0.0';
|
|
101
|
-
upper_bound TEXT := (major_range + 1) || '.0.0';
|
|
102
|
-
BEGIN
|
|
103
|
-
satisfies := (version >= lower_bound AND version < upper_bound);
|
|
104
|
-
END;
|
|
105
|
-
|
|
106
|
-
-- [Added] 2) major.x pattern '^(\d+)\.x$'
|
|
107
|
-
ELSIF range_expression ~ '^\d+\.x$' THEN
|
|
108
|
-
/*
|
|
109
|
-
e.g.) "2.x" => as long as major=2 matches, any minor and patch is OK
|
|
110
|
-
effectively works like (>=2.0.0 <3.0.0)
|
|
111
|
-
*/
|
|
112
|
-
DECLARE
|
|
113
|
-
major_range INT := split_part(range_expression, '.', 1)::INT;
|
|
114
|
-
lower_bound TEXT := major_range || '.0.0';
|
|
115
|
-
upper_bound TEXT := (major_range + 1) || '.0.0';
|
|
116
|
-
BEGIN
|
|
117
|
-
satisfies := (version >= lower_bound AND version < upper_bound);
|
|
118
|
-
END;
|
|
119
|
-
|
|
120
|
-
ELSE
|
|
121
|
-
RAISE EXCEPTION 'Unsupported range expression: %', range_expression;
|
|
122
|
-
END IF;
|
|
123
|
-
|
|
124
|
-
RETURN satisfies;
|
|
125
|
-
END;
|
|
126
|
-
$$ LANGUAGE plpgsql;
|