@hot-updater/server 0.31.4 → 0.33.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/_virtual/_rolldown/runtime.cjs +1 -1
- package/dist/_virtual/_rolldown/runtime.mjs +1 -1
- package/dist/db/createBundleDiff.cjs +19 -13
- package/dist/db/createBundleDiff.mjs +15 -9
- package/dist/db/index.cjs +7 -10
- package/dist/db/index.mjs +7 -10
- package/dist/db/pluginCore.cjs +136 -96
- package/dist/db/pluginCore.mjs +137 -97
- package/dist/db/requestBundleIdentityMap.cjs +29 -0
- package/dist/db/requestBundleIdentityMap.mjs +29 -0
- package/dist/db/schemaEnhancements.cjs +1 -1
- package/dist/db/types.d.cts +2 -1
- package/dist/db/types.d.mts +2 -1
- package/dist/db/updateArtifacts.cjs +6 -6
- package/dist/db/updateArtifacts.mjs +6 -6
- package/dist/handler.cjs +18 -8
- package/dist/handler.d.cts +9 -10
- package/dist/handler.d.mts +9 -10
- package/dist/handler.mjs +17 -7
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/node_modules/.pnpm/fumadb@0.2.2_drizzle-orm@0.44.7_@cloudflare_workers-types@4.20260313.1_@electric-sql_pg_c72c8c754becd21f6d6662e8fbd28e7f/node_modules/fumadb/dist/index.d.cts +1 -1
- package/dist/node_modules/.pnpm/fumadb@0.2.2_drizzle-orm@0.44.7_@cloudflare_workers-types@4.20260313.1_@electric-sql_pg_c72c8c754becd21f6d6662e8fbd28e7f/node_modules/fumadb/dist/index.d.mts +1 -1
- package/dist/node_modules/.pnpm/fumadb@0.2.2_drizzle-orm@0.44.7_@cloudflare_workers-types@4.20260313.1_@electric-sql_pg_c72c8c754becd21f6d6662e8fbd28e7f/node_modules/fumadb/dist/query/index.d.cts +1 -1
- package/dist/node_modules/.pnpm/fumadb@0.2.2_drizzle-orm@0.44.7_@cloudflare_workers-types@4.20260313.1_@electric-sql_pg_c72c8c754becd21f6d6662e8fbd28e7f/node_modules/fumadb/dist/query/index.d.mts +1 -1
- package/dist/packages/server/package.cjs +1 -1
- package/dist/packages/server/package.mjs +1 -1
- package/dist/runtime.cjs +10 -12
- package/dist/runtime.mjs +10 -12
- package/package.json +7 -7
- package/src/db/createBundleDiff.spec.ts +3 -0
- package/src/db/createBundleDiff.ts +27 -21
- package/src/db/index.spec.ts +36 -0
- package/src/db/index.ts +6 -10
- package/src/db/pluginCore.spec.ts +443 -0
- package/src/db/pluginCore.ts +63 -7
- package/src/db/requestBundleIdentityMap.spec.ts +56 -0
- package/src/db/requestBundleIdentityMap.ts +61 -0
- package/src/db/types.ts +2 -0
- package/src/db/updateArtifacts.ts +8 -19
- package/src/handler-standalone.integration.spec.ts +12 -0
- package/src/handler.spec.ts +117 -19
- package/src/handler.ts +47 -21
- package/src/runtime.spec.ts +46 -4
- package/src/runtime.ts +10 -12
package/dist/db/pluginCore.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { assertBundlePersistenceConstraints } from "./schemaEnhancements.mjs";
|
|
2
2
|
import { resolveManifestArtifacts } from "./updateArtifacts.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { createRequestBundleIdentityMap } from "./requestBundleIdentityMap.mjs";
|
|
4
|
+
import { getRequestUpdateBundleSeeds, semverSatisfies } from "@hot-updater/plugin-core";
|
|
4
5
|
import { NIL_UUID, isCohortEligibleForUpdate } from "@hot-updater/core";
|
|
5
6
|
//#region src/db/pluginCore.ts
|
|
6
7
|
const PAGE_SIZE = 100;
|
|
@@ -110,109 +111,148 @@ function createPluginDatabaseCore(getPlugin, resolveFileUrl, options) {
|
|
|
110
111
|
enabled: true,
|
|
111
112
|
id: { gte: minBundleId }
|
|
112
113
|
});
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return bundle.enabled && bundle.platform === args.platform && bundle.channel === channel && bundle.id.localeCompare(minBundleId) >= 0 && !!bundle.targetAppVersion && semverSatisfies(bundle.targetAppVersion, args.appVersion);
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
},
|
|
148
|
-
async getAppUpdateInfo(args, context) {
|
|
149
|
-
const info = await this.getUpdateInfo(args, context);
|
|
150
|
-
if (!info) return null;
|
|
151
|
-
const { storageUri, ...rest } = info;
|
|
152
|
-
const readStorageText = options?.readStorageText;
|
|
153
|
-
if (info.id === NIL_UUID || !readStorageText) {
|
|
154
|
-
const fileUrl = await resolveFileUrl(storageUri ?? null, context);
|
|
155
|
-
return {
|
|
156
|
-
...rest,
|
|
157
|
-
fileUrl
|
|
158
|
-
};
|
|
114
|
+
const api = {
|
|
115
|
+
async getBundleById(id, context) {
|
|
116
|
+
return getPlugin().getBundleById(id, context);
|
|
117
|
+
},
|
|
118
|
+
async getUpdateInfo(args, context) {
|
|
119
|
+
const directGetUpdateInfo = getPlugin().getUpdateInfo;
|
|
120
|
+
if (directGetUpdateInfo) return context === void 0 ? await directGetUpdateInfo(args) : await directGetUpdateInfo(args, context);
|
|
121
|
+
const channel = args.channel ?? "production";
|
|
122
|
+
const minBundleId = args.minBundleId ?? NIL_UUID;
|
|
123
|
+
const baseWhere = getBaseWhere({
|
|
124
|
+
platform: args.platform,
|
|
125
|
+
channel,
|
|
126
|
+
minBundleId
|
|
127
|
+
});
|
|
128
|
+
if (args._updateStrategy === "fingerprint") return findUpdateInfoByScanning({
|
|
129
|
+
args,
|
|
130
|
+
queryWhere: {
|
|
131
|
+
...baseWhere,
|
|
132
|
+
fingerprintHash: args.fingerprintHash
|
|
133
|
+
},
|
|
134
|
+
context,
|
|
135
|
+
isCandidate: (bundle) => {
|
|
136
|
+
return bundle.enabled && bundle.platform === args.platform && bundle.channel === channel && bundle.id.localeCompare(minBundleId) >= 0 && bundle.fingerprintHash === args.fingerprintHash;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
return findUpdateInfoByScanning({
|
|
140
|
+
args,
|
|
141
|
+
queryWhere: { ...baseWhere },
|
|
142
|
+
context,
|
|
143
|
+
isCandidate: (bundle) => {
|
|
144
|
+
return bundle.enabled && bundle.platform === args.platform && bundle.channel === channel && bundle.id.localeCompare(minBundleId) >= 0 && !!bundle.targetAppVersion && semverSatisfies(bundle.targetAppVersion, args.appVersion);
|
|
159
145
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
async getAppUpdateInfo(args, context) {
|
|
149
|
+
const info = await this.getUpdateInfo(args, context);
|
|
150
|
+
if (!info) return null;
|
|
151
|
+
const { storageUri, ...rest } = info;
|
|
152
|
+
const readStorageText = options?.readStorageText;
|
|
153
|
+
if (info.id === NIL_UUID || !readStorageText) {
|
|
154
|
+
const fileUrl = await resolveFileUrl(storageUri ?? null, context);
|
|
155
|
+
return {
|
|
166
156
|
...rest,
|
|
167
157
|
fileUrl
|
|
168
158
|
};
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
return
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
159
|
+
}
|
|
160
|
+
const requestBundleSeeds = getRequestUpdateBundleSeeds(context);
|
|
161
|
+
const requestBundles = createRequestBundleIdentityMap({
|
|
162
|
+
context,
|
|
163
|
+
loadBundleById: (bundleId, requestContext) => getPlugin().getBundleById(bundleId, requestContext),
|
|
164
|
+
seeds: requestBundleSeeds
|
|
165
|
+
});
|
|
166
|
+
const getCurrentBundle = () => {
|
|
167
|
+
if (args.bundleId === NIL_UUID) return null;
|
|
168
|
+
const seededCurrentBundle = requestBundles.peek(args.bundleId);
|
|
169
|
+
if (seededCurrentBundle || requestBundleSeeds.length > 0) return seededCurrentBundle;
|
|
170
|
+
return requestBundles.get(args.bundleId);
|
|
171
|
+
};
|
|
172
|
+
const [fileUrl, targetBundle, currentBundle] = await Promise.all([
|
|
173
|
+
resolveFileUrl(storageUri ?? null, context),
|
|
174
|
+
requestBundles.get(info.id),
|
|
175
|
+
getCurrentBundle()
|
|
176
|
+
]);
|
|
177
|
+
const baseResponse = {
|
|
178
|
+
...rest,
|
|
179
|
+
fileUrl
|
|
180
|
+
};
|
|
181
|
+
const manifestArtifacts = await resolveManifestArtifacts({
|
|
182
|
+
currentBundle,
|
|
183
|
+
resolveFileUrl,
|
|
184
|
+
readStorageText,
|
|
185
|
+
targetBundle,
|
|
186
|
+
context
|
|
187
|
+
});
|
|
188
|
+
if (!manifestArtifacts) return baseResponse;
|
|
189
|
+
return {
|
|
190
|
+
...baseResponse,
|
|
191
|
+
...manifestArtifacts
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
async getChannels(context) {
|
|
195
|
+
return getPlugin().getChannels(context);
|
|
196
|
+
},
|
|
197
|
+
async getBundles(options, context) {
|
|
198
|
+
return getPlugin().getBundles(options, context);
|
|
199
|
+
},
|
|
200
|
+
async insertBundle(bundle, context) {
|
|
201
|
+
assertBundlePersistenceConstraints(bundle);
|
|
202
|
+
await runWithMutationPlugin(async (plugin) => {
|
|
203
|
+
await plugin.appendBundle(bundle, context);
|
|
204
|
+
await plugin.commitBundle(context);
|
|
205
|
+
});
|
|
206
|
+
},
|
|
207
|
+
async updateBundleById(bundleId, newBundle, context) {
|
|
208
|
+
await runWithMutationPlugin(async (plugin) => {
|
|
209
|
+
const current = await plugin.getBundleById(bundleId, context);
|
|
210
|
+
if (!current) throw new Error("targetBundleId not found");
|
|
211
|
+
assertBundlePersistenceConstraints({
|
|
212
|
+
...current,
|
|
213
|
+
...newBundle
|
|
205
214
|
});
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
215
|
+
await plugin.updateBundle(bundleId, newBundle, context);
|
|
216
|
+
await plugin.commitBundle(context);
|
|
217
|
+
});
|
|
218
|
+
},
|
|
219
|
+
async deleteBundleById(bundleId, context) {
|
|
220
|
+
await runWithMutationPlugin(async (plugin) => {
|
|
221
|
+
const bundle = await plugin.getBundleById(bundleId, context);
|
|
222
|
+
if (!bundle) return;
|
|
223
|
+
await plugin.deleteBundle(bundle, context);
|
|
224
|
+
await plugin.commitBundle(context);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
Object.defineProperty(api, "diagnostics", {
|
|
229
|
+
configurable: true,
|
|
230
|
+
enumerable: true,
|
|
231
|
+
get() {
|
|
232
|
+
const diagnostics = getPlugin().diagnostics;
|
|
233
|
+
if (!diagnostics) {
|
|
234
|
+
Object.defineProperty(this, "diagnostics", {
|
|
235
|
+
configurable: true,
|
|
236
|
+
enumerable: true,
|
|
237
|
+
value: void 0
|
|
213
238
|
});
|
|
239
|
+
return;
|
|
214
240
|
}
|
|
215
|
-
|
|
241
|
+
const wrappedDiagnostics = {};
|
|
242
|
+
if (diagnostics.bundleIndex) wrappedDiagnostics.bundleIndex = {
|
|
243
|
+
check: (context) => getPlugin().diagnostics.bundleIndex.check(context),
|
|
244
|
+
...diagnostics.bundleIndex.repair ? { repair: (context) => getPlugin().diagnostics.bundleIndex.repair(context) } : {}
|
|
245
|
+
};
|
|
246
|
+
Object.defineProperty(this, "diagnostics", {
|
|
247
|
+
configurable: true,
|
|
248
|
+
enumerable: true,
|
|
249
|
+
value: wrappedDiagnostics
|
|
250
|
+
});
|
|
251
|
+
return wrappedDiagnostics;
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
api,
|
|
216
256
|
adapterName: getPlugin().name,
|
|
217
257
|
createMigrator: () => {
|
|
218
258
|
throw new Error("createMigrator is only available for Kysely/Prisma/Drizzle database adapters.");
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/db/requestBundleIdentityMap.ts
|
|
2
|
+
const createRequestBundleIdentityMap = ({ context, loadBundleById, seeds }) => {
|
|
3
|
+
const bundles = /* @__PURE__ */ new Map();
|
|
4
|
+
const pendingBundles = /* @__PURE__ */ new Map();
|
|
5
|
+
for (const seed of seeds) if (seed) bundles.set(seed.id, seed);
|
|
6
|
+
const get = async (bundleId) => {
|
|
7
|
+
const cachedBundle = bundles.get(bundleId);
|
|
8
|
+
if (cachedBundle) return cachedBundle;
|
|
9
|
+
const pendingBundle = pendingBundles.get(bundleId);
|
|
10
|
+
if (pendingBundle) return pendingBundle;
|
|
11
|
+
const lookup = loadBundleById(bundleId, context).then((bundle) => {
|
|
12
|
+
pendingBundles.delete(bundleId);
|
|
13
|
+
if (bundle) bundles.set(bundle.id, bundle);
|
|
14
|
+
return bundle;
|
|
15
|
+
}, (error) => {
|
|
16
|
+
pendingBundles.delete(bundleId);
|
|
17
|
+
throw error;
|
|
18
|
+
});
|
|
19
|
+
pendingBundles.set(bundleId, lookup);
|
|
20
|
+
return lookup;
|
|
21
|
+
};
|
|
22
|
+
const peek = (bundleId) => bundles.get(bundleId) ?? null;
|
|
23
|
+
return {
|
|
24
|
+
get,
|
|
25
|
+
peek
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
exports.createRequestBundleIdentityMap = createRequestBundleIdentityMap;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/db/requestBundleIdentityMap.ts
|
|
2
|
+
const createRequestBundleIdentityMap = ({ context, loadBundleById, seeds }) => {
|
|
3
|
+
const bundles = /* @__PURE__ */ new Map();
|
|
4
|
+
const pendingBundles = /* @__PURE__ */ new Map();
|
|
5
|
+
for (const seed of seeds) if (seed) bundles.set(seed.id, seed);
|
|
6
|
+
const get = async (bundleId) => {
|
|
7
|
+
const cachedBundle = bundles.get(bundleId);
|
|
8
|
+
if (cachedBundle) return cachedBundle;
|
|
9
|
+
const pendingBundle = pendingBundles.get(bundleId);
|
|
10
|
+
if (pendingBundle) return pendingBundle;
|
|
11
|
+
const lookup = loadBundleById(bundleId, context).then((bundle) => {
|
|
12
|
+
pendingBundles.delete(bundleId);
|
|
13
|
+
if (bundle) bundles.set(bundle.id, bundle);
|
|
14
|
+
return bundle;
|
|
15
|
+
}, (error) => {
|
|
16
|
+
pendingBundles.delete(bundleId);
|
|
17
|
+
throw error;
|
|
18
|
+
});
|
|
19
|
+
pendingBundles.set(bundleId, lookup);
|
|
20
|
+
return lookup;
|
|
21
|
+
};
|
|
22
|
+
const peek = (bundleId) => bundles.get(bundleId) ?? null;
|
|
23
|
+
return {
|
|
24
|
+
get,
|
|
25
|
+
peek
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
export { createRequestBundleIdentityMap };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
|
|
2
2
|
let _hot_updater_core = require("@hot-updater/core");
|
|
3
3
|
let semver = require("semver");
|
|
4
|
-
semver = require_runtime.__toESM(semver
|
|
4
|
+
semver = require_runtime.__toESM(semver);
|
|
5
5
|
//#region src/db/schemaEnhancements.ts
|
|
6
6
|
const normalizeNullableString = (value) => {
|
|
7
7
|
if (value === null || value === void 0) return null;
|
package/dist/db/types.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Provider } from "../node_modules/.pnpm/fumadb@0.2.2_drizzle-orm@0.44.7_@cloudflare_workers-types@4.20260313.1_@electric-sql_pg_c72c8c754becd21f6d6662e8fbd28e7f/node_modules/fumadb/dist/index-CMqePMTF.cjs";
|
|
2
2
|
import { PaginatedResult } from "../types/index.cjs";
|
|
3
3
|
import { AppUpdateAvailableInfo, Bundle, GetBundlesArgs, UpdateInfo } from "@hot-updater/core";
|
|
4
|
-
import { DatabaseBundleQueryOptions, DatabasePlugin, HotUpdaterContext, RuntimeStoragePlugin } from "@hot-updater/plugin-core";
|
|
4
|
+
import { DatabaseBundleQueryOptions, DatabaseDiagnostics, DatabasePlugin, HotUpdaterContext, RuntimeStoragePlugin } from "@hot-updater/plugin-core";
|
|
5
5
|
|
|
6
6
|
//#region src/db/types.d.ts
|
|
7
7
|
type DatabasePluginFactory<TContext = unknown> = () => DatabasePlugin<TContext>;
|
|
@@ -27,6 +27,7 @@ interface DatabaseAPI<TContext = unknown> {
|
|
|
27
27
|
insertBundle(bundle: Bundle, context?: HotUpdaterContext<TContext>): Promise<void>;
|
|
28
28
|
updateBundleById(bundleId: string, newBundle: Partial<Bundle>, context?: HotUpdaterContext<TContext>): Promise<void>;
|
|
29
29
|
deleteBundleById(bundleId: string, context?: HotUpdaterContext<TContext>): Promise<void>;
|
|
30
|
+
diagnostics?: DatabaseDiagnostics<TContext>;
|
|
30
31
|
}
|
|
31
32
|
type StoragePluginFactory<TContext = unknown> = () => RuntimeStoragePlugin<TContext>;
|
|
32
33
|
//#endregion
|
package/dist/db/types.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Provider } from "../node_modules/.pnpm/fumadb@0.2.2_drizzle-orm@0.44.7_@cloudflare_workers-types@4.20260313.1_@electric-sql_pg_c72c8c754becd21f6d6662e8fbd28e7f/node_modules/fumadb/dist/index-CMqePMTF.mjs";
|
|
2
2
|
import { PaginatedResult } from "../types/index.mjs";
|
|
3
|
-
import { DatabaseBundleQueryOptions, DatabasePlugin, HotUpdaterContext, RuntimeStoragePlugin } from "@hot-updater/plugin-core";
|
|
3
|
+
import { DatabaseBundleQueryOptions, DatabaseDiagnostics, DatabasePlugin, HotUpdaterContext, RuntimeStoragePlugin } from "@hot-updater/plugin-core";
|
|
4
4
|
import { AppUpdateAvailableInfo, Bundle as Bundle$1, GetBundlesArgs, UpdateInfo } from "@hot-updater/core";
|
|
5
5
|
|
|
6
6
|
//#region src/db/types.d.ts
|
|
@@ -27,6 +27,7 @@ interface DatabaseAPI<TContext = unknown> {
|
|
|
27
27
|
insertBundle(bundle: Bundle$1, context?: HotUpdaterContext<TContext>): Promise<void>;
|
|
28
28
|
updateBundleById(bundleId: string, newBundle: Partial<Bundle$1>, context?: HotUpdaterContext<TContext>): Promise<void>;
|
|
29
29
|
deleteBundleById(bundleId: string, context?: HotUpdaterContext<TContext>): Promise<void>;
|
|
30
|
+
diagnostics?: DatabaseDiagnostics<TContext>;
|
|
30
31
|
}
|
|
31
32
|
type StoragePluginFactory<TContext = unknown> = () => RuntimeStoragePlugin<TContext>;
|
|
32
33
|
//#endregion
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require("../_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
|
|
2
3
|
let _hot_updater_core = require("@hot-updater/core");
|
|
3
4
|
//#region src/db/updateArtifacts.ts
|
|
4
5
|
const HBC_ASSET_PATH_RE = /\.bundle$/;
|
|
@@ -18,11 +19,6 @@ const isBundleManifest = (value) => {
|
|
|
18
19
|
return typeof manifestAsset.fileHash === "string" && (manifestAsset.signature === void 0 || typeof manifestAsset.signature === "string");
|
|
19
20
|
});
|
|
20
21
|
};
|
|
21
|
-
const createChildStorageUri = (baseStorageUri, relativePath) => {
|
|
22
|
-
const baseUrl = new URL(baseStorageUri);
|
|
23
|
-
baseUrl.pathname = `${baseUrl.pathname.replace(/\/+$/, "")}/${relativePath.split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/")}`;
|
|
24
|
-
return baseUrl.toString();
|
|
25
|
-
};
|
|
26
22
|
const parseBundleMetadata = (value) => {
|
|
27
23
|
if (!value) return;
|
|
28
24
|
let parsedValue = value;
|
|
@@ -61,7 +57,11 @@ async function resolveChangedAssets({ assetBaseStorageUri, currentManifest, curr
|
|
|
61
57
|
const changedEntries = await Promise.all(Object.entries(targetManifest.assets).map(async ([assetPath, asset]) => {
|
|
62
58
|
if ((currentManifest?.assets[assetPath])?.fileHash === asset.fileHash) return null;
|
|
63
59
|
const usesBrotliAsset = BR_COMPRESSED_ASSET_PATH_RE.test(assetPath);
|
|
64
|
-
const storageUri =
|
|
60
|
+
const storageUri = (0, _hot_updater_plugin_core.resolveManifestAssetStorageUri)({
|
|
61
|
+
assetBaseStorageUri,
|
|
62
|
+
assetPath: usesBrotliAsset ? `${assetPath}.br` : assetPath,
|
|
63
|
+
fileHash: asset.fileHash
|
|
64
|
+
});
|
|
65
65
|
const patch = patchDescriptor?.assetPath === assetPath ? patchDescriptor.patch : null;
|
|
66
66
|
let fileUrl = null;
|
|
67
67
|
try {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveManifestAssetStorageUri } from "@hot-updater/plugin-core";
|
|
1
2
|
import { getAssetBaseStorageUri, getBundlePatch, getManifestFileHash, getManifestStorageUri, stripBundleArtifactMetadata } from "@hot-updater/core";
|
|
2
3
|
//#region src/db/updateArtifacts.ts
|
|
3
4
|
const HBC_ASSET_PATH_RE = /\.bundle$/;
|
|
@@ -17,11 +18,6 @@ const isBundleManifest = (value) => {
|
|
|
17
18
|
return typeof manifestAsset.fileHash === "string" && (manifestAsset.signature === void 0 || typeof manifestAsset.signature === "string");
|
|
18
19
|
});
|
|
19
20
|
};
|
|
20
|
-
const createChildStorageUri = (baseStorageUri, relativePath) => {
|
|
21
|
-
const baseUrl = new URL(baseStorageUri);
|
|
22
|
-
baseUrl.pathname = `${baseUrl.pathname.replace(/\/+$/, "")}/${relativePath.split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/")}`;
|
|
23
|
-
return baseUrl.toString();
|
|
24
|
-
};
|
|
25
21
|
const parseBundleMetadata = (value) => {
|
|
26
22
|
if (!value) return;
|
|
27
23
|
let parsedValue = value;
|
|
@@ -60,7 +56,11 @@ async function resolveChangedAssets({ assetBaseStorageUri, currentManifest, curr
|
|
|
60
56
|
const changedEntries = await Promise.all(Object.entries(targetManifest.assets).map(async ([assetPath, asset]) => {
|
|
61
57
|
if ((currentManifest?.assets[assetPath])?.fileHash === asset.fileHash) return null;
|
|
62
58
|
const usesBrotliAsset = BR_COMPRESSED_ASSET_PATH_RE.test(assetPath);
|
|
63
|
-
const storageUri =
|
|
59
|
+
const storageUri = resolveManifestAssetStorageUri({
|
|
60
|
+
assetBaseStorageUri,
|
|
61
|
+
assetPath: usesBrotliAsset ? `${assetPath}.br` : assetPath,
|
|
62
|
+
fileHash: asset.fileHash
|
|
63
|
+
});
|
|
64
64
|
const patch = patchDescriptor?.assetPath === assetPath ? patchDescriptor.patch : null;
|
|
65
65
|
let fileUrl = null;
|
|
66
66
|
try {
|
package/dist/handler.cjs
CHANGED
|
@@ -2,7 +2,7 @@ const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
|
|
|
2
2
|
const require_internalRouter = require("./internalRouter.cjs");
|
|
3
3
|
const require_version = require("./version.cjs");
|
|
4
4
|
let semver = require("semver");
|
|
5
|
-
semver = require_runtime.__toESM(semver
|
|
5
|
+
semver = require_runtime.__toESM(semver);
|
|
6
6
|
//#region src/handler.ts
|
|
7
7
|
var HandlerBadRequestError = class extends Error {
|
|
8
8
|
constructor(message) {
|
|
@@ -12,6 +12,8 @@ var HandlerBadRequestError = class extends Error {
|
|
|
12
12
|
};
|
|
13
13
|
const SDK_VERSION_HEADER = "Hot-Updater-SDK-Version";
|
|
14
14
|
const EXPLICIT_NO_UPDATE_MIN_SDK_VERSION = "0.31.0";
|
|
15
|
+
const DEFAULT_BUNDLE_LIST_LIMIT = 50;
|
|
16
|
+
const MAX_BUNDLE_LIST_LIMIT = 100;
|
|
15
17
|
const supportsExplicitNoUpdateResponse = (request) => {
|
|
16
18
|
const sdkVersion = request.headers.get(SDK_VERSION_HEADER)?.trim();
|
|
17
19
|
if (!sdkVersion) return false;
|
|
@@ -61,6 +63,13 @@ const parseStringArraySearchParam = (url, key) => {
|
|
|
61
63
|
const values = url.searchParams.getAll(key);
|
|
62
64
|
return values.length > 0 ? values : void 0;
|
|
63
65
|
};
|
|
66
|
+
const parsePositiveIntegerSearchParam = (url, key, defaultValue, maxValue) => {
|
|
67
|
+
const value = url.searchParams.get(key);
|
|
68
|
+
if (value === null) return defaultValue;
|
|
69
|
+
const parsed = Number(value);
|
|
70
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > maxValue) throw new HandlerBadRequestError(`The '${key}' query parameter must be a positive integer between 1 and ${maxValue}.`);
|
|
71
|
+
return parsed;
|
|
72
|
+
};
|
|
64
73
|
const requirePlatformParam = (params) => {
|
|
65
74
|
const platform = requireRouteParam(params, "platform");
|
|
66
75
|
if (!isPlatform(platform)) throw new HandlerBadRequestError(`Invalid platform: ${platform}. Expected 'ios' or 'android'.`);
|
|
@@ -129,7 +138,7 @@ const handleGetBundles = async (_params, request, api, context) => {
|
|
|
129
138
|
const url = new URL(request.url);
|
|
130
139
|
const channel = url.searchParams.get("channel") ?? void 0;
|
|
131
140
|
const platform = url.searchParams.get("platform");
|
|
132
|
-
const limit =
|
|
141
|
+
const limit = parsePositiveIntegerSearchParam(url, "limit", DEFAULT_BUNDLE_LIST_LIMIT, MAX_BUNDLE_LIST_LIMIT);
|
|
133
142
|
const pageParam = url.searchParams.get("page");
|
|
134
143
|
const offset = url.searchParams.get("offset");
|
|
135
144
|
const after = url.searchParams.get("after") ?? void 0;
|
|
@@ -231,18 +240,19 @@ const routes = {
|
|
|
231
240
|
*/
|
|
232
241
|
function createHandler(api, options = {}) {
|
|
233
242
|
const basePath = options.basePath ?? "/api";
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
243
|
+
const routeOptions = {
|
|
244
|
+
updateCheck: options.routes?.updateCheck ?? true,
|
|
245
|
+
bundles: options.routes?.bundles ?? false
|
|
246
|
+
};
|
|
237
247
|
const router = require_internalRouter.createRouter();
|
|
238
|
-
|
|
239
|
-
if (
|
|
248
|
+
require_internalRouter.addRoute(router, "GET", "/version", "version");
|
|
249
|
+
if (routeOptions.updateCheck) {
|
|
240
250
|
require_internalRouter.addRoute(router, "GET", "/fingerprint/:platform/:fingerprintHash/:channel/:minBundleId/:bundleId", "fingerprintUpdateWithCohort");
|
|
241
251
|
require_internalRouter.addRoute(router, "GET", "/fingerprint/:platform/:fingerprintHash/:channel/:minBundleId/:bundleId/:cohort", "fingerprintUpdateWithCohort");
|
|
242
252
|
require_internalRouter.addRoute(router, "GET", "/app-version/:platform/:appVersion/:channel/:minBundleId/:bundleId", "appVersionUpdateWithCohort");
|
|
243
253
|
require_internalRouter.addRoute(router, "GET", "/app-version/:platform/:appVersion/:channel/:minBundleId/:bundleId/:cohort", "appVersionUpdateWithCohort");
|
|
244
254
|
}
|
|
245
|
-
if (
|
|
255
|
+
if (routeOptions.bundles) {
|
|
246
256
|
require_internalRouter.addRoute(router, "GET", "/api/bundles/channels", "getChannels");
|
|
247
257
|
require_internalRouter.addRoute(router, "GET", "/api/bundles/:id", "getBundle");
|
|
248
258
|
require_internalRouter.addRoute(router, "GET", "/api/bundles", "getBundles");
|
package/dist/handler.d.cts
CHANGED
|
@@ -18,27 +18,26 @@ interface HandlerOptions {
|
|
|
18
18
|
* @default "/api"
|
|
19
19
|
*/
|
|
20
20
|
basePath?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Route groups to mount. Omit this option to use the default route groups.
|
|
23
|
+
* When provided, both route groups must be specified explicitly.
|
|
24
|
+
* The `/version` endpoint is always mounted for diagnostics.
|
|
25
|
+
*/
|
|
21
26
|
routes?: HandlerRoutes;
|
|
22
27
|
}
|
|
23
28
|
interface HandlerRoutes {
|
|
24
29
|
/**
|
|
25
30
|
* Controls whether update-check routes are mounted.
|
|
26
|
-
*
|
|
27
|
-
*/
|
|
28
|
-
updateCheck?: boolean;
|
|
29
|
-
/**
|
|
30
|
-
* Controls whether the `/version` endpoint is mounted.
|
|
31
|
-
* Useful for diagnostics and lightweight health/version checks.
|
|
32
|
-
* @default true
|
|
31
|
+
* Defaults to `true` only when `routes` is omitted.
|
|
33
32
|
*/
|
|
34
|
-
|
|
33
|
+
updateCheck: boolean;
|
|
35
34
|
/**
|
|
36
35
|
* Controls whether bundle management routes are mounted.
|
|
37
36
|
* This includes `/api/bundles*`, which are used by the
|
|
38
37
|
* CLI `standaloneRepository` plugin.
|
|
39
|
-
*
|
|
38
|
+
* Defaults to `false` only when `routes` is omitted.
|
|
40
39
|
*/
|
|
41
|
-
bundles
|
|
40
|
+
bundles: boolean;
|
|
42
41
|
}
|
|
43
42
|
/**
|
|
44
43
|
* Creates a Web Standard Request handler for Hot Updater API
|
package/dist/handler.d.mts
CHANGED
|
@@ -18,27 +18,26 @@ interface HandlerOptions {
|
|
|
18
18
|
* @default "/api"
|
|
19
19
|
*/
|
|
20
20
|
basePath?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Route groups to mount. Omit this option to use the default route groups.
|
|
23
|
+
* When provided, both route groups must be specified explicitly.
|
|
24
|
+
* The `/version` endpoint is always mounted for diagnostics.
|
|
25
|
+
*/
|
|
21
26
|
routes?: HandlerRoutes;
|
|
22
27
|
}
|
|
23
28
|
interface HandlerRoutes {
|
|
24
29
|
/**
|
|
25
30
|
* Controls whether update-check routes are mounted.
|
|
26
|
-
*
|
|
27
|
-
*/
|
|
28
|
-
updateCheck?: boolean;
|
|
29
|
-
/**
|
|
30
|
-
* Controls whether the `/version` endpoint is mounted.
|
|
31
|
-
* Useful for diagnostics and lightweight health/version checks.
|
|
32
|
-
* @default true
|
|
31
|
+
* Defaults to `true` only when `routes` is omitted.
|
|
33
32
|
*/
|
|
34
|
-
|
|
33
|
+
updateCheck: boolean;
|
|
35
34
|
/**
|
|
36
35
|
* Controls whether bundle management routes are mounted.
|
|
37
36
|
* This includes `/api/bundles*`, which are used by the
|
|
38
37
|
* CLI `standaloneRepository` plugin.
|
|
39
|
-
*
|
|
38
|
+
* Defaults to `false` only when `routes` is omitted.
|
|
40
39
|
*/
|
|
41
|
-
bundles
|
|
40
|
+
bundles: boolean;
|
|
42
41
|
}
|
|
43
42
|
/**
|
|
44
43
|
* Creates a Web Standard Request handler for Hot Updater API
|
package/dist/handler.mjs
CHANGED
|
@@ -10,6 +10,8 @@ var HandlerBadRequestError = class extends Error {
|
|
|
10
10
|
};
|
|
11
11
|
const SDK_VERSION_HEADER = "Hot-Updater-SDK-Version";
|
|
12
12
|
const EXPLICIT_NO_UPDATE_MIN_SDK_VERSION = "0.31.0";
|
|
13
|
+
const DEFAULT_BUNDLE_LIST_LIMIT = 50;
|
|
14
|
+
const MAX_BUNDLE_LIST_LIMIT = 100;
|
|
13
15
|
const supportsExplicitNoUpdateResponse = (request) => {
|
|
14
16
|
const sdkVersion = request.headers.get(SDK_VERSION_HEADER)?.trim();
|
|
15
17
|
if (!sdkVersion) return false;
|
|
@@ -59,6 +61,13 @@ const parseStringArraySearchParam = (url, key) => {
|
|
|
59
61
|
const values = url.searchParams.getAll(key);
|
|
60
62
|
return values.length > 0 ? values : void 0;
|
|
61
63
|
};
|
|
64
|
+
const parsePositiveIntegerSearchParam = (url, key, defaultValue, maxValue) => {
|
|
65
|
+
const value = url.searchParams.get(key);
|
|
66
|
+
if (value === null) return defaultValue;
|
|
67
|
+
const parsed = Number(value);
|
|
68
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > maxValue) throw new HandlerBadRequestError(`The '${key}' query parameter must be a positive integer between 1 and ${maxValue}.`);
|
|
69
|
+
return parsed;
|
|
70
|
+
};
|
|
62
71
|
const requirePlatformParam = (params) => {
|
|
63
72
|
const platform = requireRouteParam(params, "platform");
|
|
64
73
|
if (!isPlatform(platform)) throw new HandlerBadRequestError(`Invalid platform: ${platform}. Expected 'ios' or 'android'.`);
|
|
@@ -127,7 +136,7 @@ const handleGetBundles = async (_params, request, api, context) => {
|
|
|
127
136
|
const url = new URL(request.url);
|
|
128
137
|
const channel = url.searchParams.get("channel") ?? void 0;
|
|
129
138
|
const platform = url.searchParams.get("platform");
|
|
130
|
-
const limit =
|
|
139
|
+
const limit = parsePositiveIntegerSearchParam(url, "limit", DEFAULT_BUNDLE_LIST_LIMIT, MAX_BUNDLE_LIST_LIMIT);
|
|
131
140
|
const pageParam = url.searchParams.get("page");
|
|
132
141
|
const offset = url.searchParams.get("offset");
|
|
133
142
|
const after = url.searchParams.get("after") ?? void 0;
|
|
@@ -229,18 +238,19 @@ const routes = {
|
|
|
229
238
|
*/
|
|
230
239
|
function createHandler(api, options = {}) {
|
|
231
240
|
const basePath = options.basePath ?? "/api";
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
241
|
+
const routeOptions = {
|
|
242
|
+
updateCheck: options.routes?.updateCheck ?? true,
|
|
243
|
+
bundles: options.routes?.bundles ?? false
|
|
244
|
+
};
|
|
235
245
|
const router = createRouter();
|
|
236
|
-
|
|
237
|
-
if (
|
|
246
|
+
addRoute(router, "GET", "/version", "version");
|
|
247
|
+
if (routeOptions.updateCheck) {
|
|
238
248
|
addRoute(router, "GET", "/fingerprint/:platform/:fingerprintHash/:channel/:minBundleId/:bundleId", "fingerprintUpdateWithCohort");
|
|
239
249
|
addRoute(router, "GET", "/fingerprint/:platform/:fingerprintHash/:channel/:minBundleId/:bundleId/:cohort", "fingerprintUpdateWithCohort");
|
|
240
250
|
addRoute(router, "GET", "/app-version/:platform/:appVersion/:channel/:minBundleId/:bundleId", "appVersionUpdateWithCohort");
|
|
241
251
|
addRoute(router, "GET", "/app-version/:platform/:appVersion/:channel/:minBundleId/:bundleId/:cohort", "appVersionUpdateWithCohort");
|
|
242
252
|
}
|
|
243
|
-
if (
|
|
253
|
+
if (routeOptions.bundles) {
|
|
244
254
|
addRoute(router, "GET", "/api/bundles/channels", "getChannels");
|
|
245
255
|
addRoute(router, "GET", "/api/bundles/:id", "getBundle");
|
|
246
256
|
addRoute(router, "GET", "/api/bundles", "getBundles");
|
package/dist/index.d.cts
CHANGED
|
@@ -4,4 +4,4 @@ import { HotUpdaterClient, HotUpdaterDB, Migrator } from "./db/ormCore.cjs";
|
|
|
4
4
|
import { HOT_UPDATER_SERVER_VERSION } from "./version.cjs";
|
|
5
5
|
import { CreateHotUpdaterOptions, HotUpdaterAPI, createHotUpdater } from "./db/index.cjs";
|
|
6
6
|
import { Bundle, ChannelsResponse, DataResponse, Paginated, PaginatedResult, PaginationInfo, PaginationOptions } from "./types/index.cjs";
|
|
7
|
-
export {
|
|
7
|
+
export { Bundle, ChannelsResponse, CreateBundleDiffDependencies, CreateBundleDiffInput, CreateBundleDiffOptions, CreateHotUpdaterOptions, DataResponse, HOT_UPDATER_SERVER_VERSION, HandlerAPI, HandlerOptions, HandlerRoutes, HotUpdaterAPI, HotUpdaterClient, HotUpdaterDB, Migrator, Paginated, PaginatedResult, PaginationInfo, PaginationOptions, createBundleDiff, createHandler, createHotUpdater };
|
package/dist/index.d.mts
CHANGED
|
@@ -4,4 +4,4 @@ import { HotUpdaterClient, HotUpdaterDB, Migrator } from "./db/ormCore.mjs";
|
|
|
4
4
|
import { HOT_UPDATER_SERVER_VERSION } from "./version.mjs";
|
|
5
5
|
import { CreateHotUpdaterOptions, HotUpdaterAPI, createHotUpdater } from "./db/index.mjs";
|
|
6
6
|
import { Bundle, ChannelsResponse, DataResponse, Paginated, PaginatedResult, PaginationInfo, PaginationOptions } from "./types/index.mjs";
|
|
7
|
-
export {
|
|
7
|
+
export { Bundle, ChannelsResponse, CreateBundleDiffDependencies, CreateBundleDiffInput, CreateBundleDiffOptions, CreateHotUpdaterOptions, DataResponse, HOT_UPDATER_SERVER_VERSION, HandlerAPI, HandlerOptions, HandlerRoutes, HotUpdaterAPI, HotUpdaterClient, HotUpdaterDB, Migrator, Paginated, PaginatedResult, PaginationInfo, PaginationOptions, createBundleDiff, createHandler, createHotUpdater };
|
|
@@ -46,4 +46,4 @@ interface FumaDBFactory<Schemas extends AnySchema[]> {
|
|
|
46
46
|
}
|
|
47
47
|
type InferFumaDB<Factory extends FumaDBFactory<any>> = Factory extends FumaDBFactory<infer Schemas> ? FumaDB<Schemas> : never;
|
|
48
48
|
//#endregion
|
|
49
|
-
export type
|
|
49
|
+
export { type FumaDBFactory, type InferFumaDB };
|