@hot-updater/standalone 1.1.4 → 1.1.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/dist/index.cjs +46 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.mts +11 -1
- package/dist/index.mjs +46 -1
- package/package.json +4 -9
package/dist/index.cjs
CHANGED
|
@@ -240,8 +240,12 @@ const defaultRoutes = {
|
|
|
240
240
|
put: () => ({ path: "/upload" }),
|
|
241
241
|
get: () => ({ path: "/get" }),
|
|
242
242
|
exists: () => ({ path: "/exists" }),
|
|
243
|
-
delete: () => ({ path: "/delete" })
|
|
243
|
+
delete: () => ({ path: "/delete" }),
|
|
244
|
+
listObjects: () => ({ path: "/list" }),
|
|
245
|
+
deleteObjects: () => ({ path: "/delete-objects" })
|
|
244
246
|
};
|
|
247
|
+
/** Keys sent in one `deleteObjects` request. */
|
|
248
|
+
const DELETE_BATCH_SIZE = 1e3;
|
|
245
249
|
const createRoute = (defaultRoute, customRoute) => ({
|
|
246
250
|
path: customRoute?.path ?? defaultRoute.path,
|
|
247
251
|
headers: {
|
|
@@ -259,6 +263,14 @@ const standaloneStorage = (config) => {
|
|
|
259
263
|
const custom = config.routes?.[name];
|
|
260
264
|
return createRoute(fallback(...args), custom?.(...args));
|
|
261
265
|
};
|
|
266
|
+
const postJson = (path, headers, body) => fetch(`${config.baseUrl}${path}`, {
|
|
267
|
+
method: "POST",
|
|
268
|
+
headers: {
|
|
269
|
+
"content-type": "application/json",
|
|
270
|
+
...getHeaders(headers)
|
|
271
|
+
},
|
|
272
|
+
body: JSON.stringify(body)
|
|
273
|
+
});
|
|
262
274
|
const requestStorageUri = async (routeName, storageUri) => {
|
|
263
275
|
(0, _hot_updater_plugin_core.parseStorageUri)(storageUri, config.protocol);
|
|
264
276
|
const { path, headers } = resolveRoute(routeName, [{ storageUri }]);
|
|
@@ -310,6 +322,39 @@ const standaloneStorage = (config) => {
|
|
|
310
322
|
async delete({ storageUri }) {
|
|
311
323
|
await requestStorageUri("delete", storageUri);
|
|
312
324
|
return { deleted: true };
|
|
325
|
+
},
|
|
326
|
+
async listObjects(prefix) {
|
|
327
|
+
const { path, headers } = resolveRoute("listObjects", [{ prefix }]);
|
|
328
|
+
const objects = [];
|
|
329
|
+
let cursor;
|
|
330
|
+
do {
|
|
331
|
+
const response = await postJson(path, headers, {
|
|
332
|
+
prefix,
|
|
333
|
+
cursor
|
|
334
|
+
});
|
|
335
|
+
if (response.status === 404) throw new Error(`The storage service has no ${path} route, which storage prune needs.`);
|
|
336
|
+
if (!response.ok) throw new Error(`Failed to list storage objects: ${response.statusText}`);
|
|
337
|
+
const page = await response.json();
|
|
338
|
+
for (const object of page.objects) {
|
|
339
|
+
(0, _hot_updater_plugin_core.parseStorageUri)(object.storageUri, config.protocol);
|
|
340
|
+
objects.push({
|
|
341
|
+
key: object.key,
|
|
342
|
+
storageUri: object.storageUri,
|
|
343
|
+
size: object.size,
|
|
344
|
+
...object.lastModifiedAt === void 0 ? {} : { lastModifiedAt: new Date(object.lastModifiedAt) }
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
cursor = page.cursor;
|
|
348
|
+
} while (cursor !== void 0);
|
|
349
|
+
return objects;
|
|
350
|
+
},
|
|
351
|
+
async deleteObjects(keys) {
|
|
352
|
+
for (let offset = 0; offset < keys.length; offset += DELETE_BATCH_SIZE) {
|
|
353
|
+
const batch = keys.slice(offset, offset + DELETE_BATCH_SIZE);
|
|
354
|
+
const { path, headers } = resolveRoute("deleteObjects", [{ keys: batch }]);
|
|
355
|
+
const response = await postJson(path, headers, { keys: batch });
|
|
356
|
+
if (!response.ok) throw new Error(`Failed to delete storage objects: ${response.statusText}`);
|
|
357
|
+
}
|
|
313
358
|
}
|
|
314
359
|
});
|
|
315
360
|
};
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HotUpdaterCoreApi, RemoteDatabase, StorageDeleteInput, StorageExistsInput, StorageGetInput, StoragePutInput } from "@hot-updater/plugin-core";
|
|
1
|
+
import { HotUpdaterCoreApi, RemoteDatabase, StorageDeleteInput, StorageExistsInput, StorageGetInput, StorageObject, StoragePutInput } from "@hot-updater/plugin-core";
|
|
2
2
|
//#region src/standaloneRoutes.d.ts
|
|
3
3
|
interface StandaloneRepositoryConfig {
|
|
4
4
|
/** Base URL of the Hot Updater admin handler. */
|
|
@@ -48,6 +48,14 @@ interface StorageRoutes {
|
|
|
48
48
|
get: (input: StorageGetInput) => RouteConfig;
|
|
49
49
|
exists: (input: StorageExistsInput) => RouteConfig;
|
|
50
50
|
delete: (input: StorageDeleteInput) => RouteConfig;
|
|
51
|
+
/** Lists objects for `hot-updater storage prune`. */
|
|
52
|
+
listObjects: (input: {
|
|
53
|
+
readonly prefix?: string;
|
|
54
|
+
}) => RouteConfig;
|
|
55
|
+
/** Deletes exact keys for `hot-updater storage prune --yes`. */
|
|
56
|
+
deleteObjects: (input: {
|
|
57
|
+
readonly keys: readonly string[];
|
|
58
|
+
}) => RouteConfig;
|
|
51
59
|
}
|
|
52
60
|
interface StandaloneStorageConfig {
|
|
53
61
|
baseUrl: string;
|
|
@@ -71,6 +79,8 @@ declare const standaloneStorage: (config: StandaloneStorageConfig) => {
|
|
|
71
79
|
readonly delete: ({ storageUri }: StorageDeleteInput) => Promise<{
|
|
72
80
|
deleted: true;
|
|
73
81
|
}>;
|
|
82
|
+
readonly listObjects: (prefix?: string) => Promise<StorageObject[]>;
|
|
83
|
+
readonly deleteObjects: (keys: readonly string[]) => Promise<void>;
|
|
74
84
|
};
|
|
75
85
|
//#endregion
|
|
76
86
|
export { RouteConfig, STANDALONE_ADMIN_PROTOCOL, StandaloneDatabaseError, StandaloneRepository, type StandaloneRepositoryConfig, StandaloneStorageConfig, StorageRoutes, createStandaloneCoreApi, standaloneRepository, standaloneStorage };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HotUpdaterCoreApi, RemoteDatabase, StorageDeleteInput, StorageExistsInput, StorageGetInput, StoragePutInput } from "@hot-updater/plugin-core";
|
|
1
|
+
import { HotUpdaterCoreApi, RemoteDatabase, StorageDeleteInput, StorageExistsInput, StorageGetInput, StorageObject, StoragePutInput } from "@hot-updater/plugin-core";
|
|
2
2
|
//#region src/standaloneRoutes.d.ts
|
|
3
3
|
interface StandaloneRepositoryConfig {
|
|
4
4
|
/** Base URL of the Hot Updater admin handler. */
|
|
@@ -48,6 +48,14 @@ interface StorageRoutes {
|
|
|
48
48
|
get: (input: StorageGetInput) => RouteConfig;
|
|
49
49
|
exists: (input: StorageExistsInput) => RouteConfig;
|
|
50
50
|
delete: (input: StorageDeleteInput) => RouteConfig;
|
|
51
|
+
/** Lists objects for `hot-updater storage prune`. */
|
|
52
|
+
listObjects: (input: {
|
|
53
|
+
readonly prefix?: string;
|
|
54
|
+
}) => RouteConfig;
|
|
55
|
+
/** Deletes exact keys for `hot-updater storage prune --yes`. */
|
|
56
|
+
deleteObjects: (input: {
|
|
57
|
+
readonly keys: readonly string[];
|
|
58
|
+
}) => RouteConfig;
|
|
51
59
|
}
|
|
52
60
|
interface StandaloneStorageConfig {
|
|
53
61
|
baseUrl: string;
|
|
@@ -71,6 +79,8 @@ declare const standaloneStorage: (config: StandaloneStorageConfig) => {
|
|
|
71
79
|
readonly delete: ({ storageUri }: StorageDeleteInput) => Promise<{
|
|
72
80
|
deleted: true;
|
|
73
81
|
}>;
|
|
82
|
+
readonly listObjects: (prefix?: string) => Promise<StorageObject[]>;
|
|
83
|
+
readonly deleteObjects: (keys: readonly string[]) => Promise<void>;
|
|
74
84
|
};
|
|
75
85
|
//#endregion
|
|
76
86
|
export { RouteConfig, STANDALONE_ADMIN_PROTOCOL, StandaloneDatabaseError, StandaloneRepository, type StandaloneRepositoryConfig, StandaloneStorageConfig, StorageRoutes, createStandaloneCoreApi, standaloneRepository, standaloneStorage };
|
package/dist/index.mjs
CHANGED
|
@@ -239,8 +239,12 @@ const defaultRoutes = {
|
|
|
239
239
|
put: () => ({ path: "/upload" }),
|
|
240
240
|
get: () => ({ path: "/get" }),
|
|
241
241
|
exists: () => ({ path: "/exists" }),
|
|
242
|
-
delete: () => ({ path: "/delete" })
|
|
242
|
+
delete: () => ({ path: "/delete" }),
|
|
243
|
+
listObjects: () => ({ path: "/list" }),
|
|
244
|
+
deleteObjects: () => ({ path: "/delete-objects" })
|
|
243
245
|
};
|
|
246
|
+
/** Keys sent in one `deleteObjects` request. */
|
|
247
|
+
const DELETE_BATCH_SIZE = 1e3;
|
|
244
248
|
const createRoute = (defaultRoute, customRoute) => ({
|
|
245
249
|
path: customRoute?.path ?? defaultRoute.path,
|
|
246
250
|
headers: {
|
|
@@ -258,6 +262,14 @@ const standaloneStorage = (config) => {
|
|
|
258
262
|
const custom = config.routes?.[name];
|
|
259
263
|
return createRoute(fallback(...args), custom?.(...args));
|
|
260
264
|
};
|
|
265
|
+
const postJson = (path, headers, body) => fetch(`${config.baseUrl}${path}`, {
|
|
266
|
+
method: "POST",
|
|
267
|
+
headers: {
|
|
268
|
+
"content-type": "application/json",
|
|
269
|
+
...getHeaders(headers)
|
|
270
|
+
},
|
|
271
|
+
body: JSON.stringify(body)
|
|
272
|
+
});
|
|
261
273
|
const requestStorageUri = async (routeName, storageUri) => {
|
|
262
274
|
parseStorageUri(storageUri, config.protocol);
|
|
263
275
|
const { path, headers } = resolveRoute(routeName, [{ storageUri }]);
|
|
@@ -309,6 +321,39 @@ const standaloneStorage = (config) => {
|
|
|
309
321
|
async delete({ storageUri }) {
|
|
310
322
|
await requestStorageUri("delete", storageUri);
|
|
311
323
|
return { deleted: true };
|
|
324
|
+
},
|
|
325
|
+
async listObjects(prefix) {
|
|
326
|
+
const { path, headers } = resolveRoute("listObjects", [{ prefix }]);
|
|
327
|
+
const objects = [];
|
|
328
|
+
let cursor;
|
|
329
|
+
do {
|
|
330
|
+
const response = await postJson(path, headers, {
|
|
331
|
+
prefix,
|
|
332
|
+
cursor
|
|
333
|
+
});
|
|
334
|
+
if (response.status === 404) throw new Error(`The storage service has no ${path} route, which storage prune needs.`);
|
|
335
|
+
if (!response.ok) throw new Error(`Failed to list storage objects: ${response.statusText}`);
|
|
336
|
+
const page = await response.json();
|
|
337
|
+
for (const object of page.objects) {
|
|
338
|
+
parseStorageUri(object.storageUri, config.protocol);
|
|
339
|
+
objects.push({
|
|
340
|
+
key: object.key,
|
|
341
|
+
storageUri: object.storageUri,
|
|
342
|
+
size: object.size,
|
|
343
|
+
...object.lastModifiedAt === void 0 ? {} : { lastModifiedAt: new Date(object.lastModifiedAt) }
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
cursor = page.cursor;
|
|
347
|
+
} while (cursor !== void 0);
|
|
348
|
+
return objects;
|
|
349
|
+
},
|
|
350
|
+
async deleteObjects(keys) {
|
|
351
|
+
for (let offset = 0; offset < keys.length; offset += DELETE_BATCH_SIZE) {
|
|
352
|
+
const batch = keys.slice(offset, offset + DELETE_BATCH_SIZE);
|
|
353
|
+
const { path, headers } = resolveRoute("deleteObjects", [{ keys: batch }]);
|
|
354
|
+
const response = await postJson(path, headers, { keys: batch });
|
|
355
|
+
if (!response.ok) throw new Error(`Failed to delete storage objects: ${response.statusText}`);
|
|
356
|
+
}
|
|
312
357
|
}
|
|
313
358
|
});
|
|
314
359
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/standalone",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "React Native OTA solution for self-hosted",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"access": "public"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@hot-updater/protocol": "1.1.
|
|
46
|
-
"@hot-updater/plugin-core": "1.1.
|
|
45
|
+
"@hot-updater/protocol": "1.1.6",
|
|
46
|
+
"@hot-updater/plugin-core": "1.1.6"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"mime": "2.6.0",
|
|
@@ -52,12 +52,7 @@
|
|
|
52
52
|
"msw": "^2.7.0",
|
|
53
53
|
"verkit": "0.4.0",
|
|
54
54
|
"vitest": "4.1.4",
|
|
55
|
-
"@hot-updater/test-utils": "1.1.
|
|
56
|
-
},
|
|
57
|
-
"nx": {
|
|
58
|
-
"implicitDependencies": [
|
|
59
|
-
"!@hot-updater/test-utils"
|
|
60
|
-
]
|
|
55
|
+
"@hot-updater/test-utils": "1.1.6"
|
|
61
56
|
},
|
|
62
57
|
"scripts": {
|
|
63
58
|
"build": "tsdown",
|