@hot-updater/server 0.36.7 → 1.0.0-rc.1
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/adapters/databaseConstraintErrors.cjs +23 -0
- package/dist/adapters/databaseConstraintErrors.mjs +21 -0
- package/dist/adapters/databasePluginUtils.cjs +117 -0
- package/dist/adapters/databasePluginUtils.mjs +106 -0
- package/dist/adapters/drizzle.cjs +53 -176
- package/dist/adapters/drizzle.d.cts +6 -4
- package/dist/adapters/drizzle.d.mts +6 -4
- package/dist/adapters/drizzle.mjs +54 -177
- package/dist/adapters/drizzleCrud.cjs +250 -0
- package/dist/adapters/drizzleCrud.mjs +249 -0
- package/dist/adapters/drizzleLazyDB.cjs +84 -36
- package/dist/adapters/drizzleLazyDB.mjs +84 -36
- package/dist/adapters/drizzleQuery.cjs +68 -0
- package/dist/adapters/drizzleQuery.mjs +67 -0
- package/dist/adapters/kysely.cjs +18 -138
- package/dist/adapters/kysely.d.cts +3 -12
- package/dist/adapters/kysely.d.mts +3 -12
- package/dist/adapters/kysely.mjs +19 -139
- package/dist/adapters/kyselyCrud.cjs +254 -0
- package/dist/adapters/kyselyCrud.mjs +253 -0
- package/dist/adapters/kyselyQuery.cjs +56 -0
- package/dist/adapters/kyselyQuery.mjs +55 -0
- package/dist/adapters/mongodb.cjs +21 -174
- package/dist/adapters/mongodb.d.cts +3 -2
- package/dist/adapters/mongodb.d.mts +3 -2
- package/dist/adapters/mongodb.mjs +21 -174
- package/dist/adapters/mongodbCollections.cjs +36 -0
- package/dist/adapters/mongodbCollections.mjs +30 -0
- package/dist/adapters/mongodbQuery.cjs +77 -0
- package/dist/adapters/mongodbQuery.mjs +70 -0
- package/dist/adapters/mongodbReads.cjs +148 -0
- package/dist/adapters/mongodbReads.mjs +147 -0
- package/dist/adapters/mongodbWrites.cjs +134 -0
- package/dist/adapters/mongodbWrites.mjs +133 -0
- package/dist/adapters/prisma.cjs +205 -180
- package/dist/adapters/prisma.d.cts +3 -3
- package/dist/adapters/prisma.d.mts +3 -3
- package/dist/adapters/prisma.mjs +206 -181
- package/dist/adapters/prismaQuery.cjs +58 -0
- package/dist/adapters/prismaQuery.mjs +56 -0
- package/dist/adapters/prismaRows.cjs +198 -0
- package/dist/adapters/prismaRows.mjs +188 -0
- package/dist/apiKeys.cjs +139 -0
- package/dist/apiKeys.d.cts +50 -0
- package/dist/apiKeys.d.mts +50 -0
- package/dist/apiKeys.mjs +133 -0
- package/dist/createHotUpdaterCore.cjs +48 -26
- package/dist/createHotUpdaterCore.d.cts +49 -17
- package/dist/createHotUpdaterCore.d.mts +49 -17
- package/dist/createHotUpdaterCore.mjs +51 -29
- package/dist/db/createBundleDiff.cjs +45 -68
- package/dist/db/createBundleDiff.d.cts +3 -3
- package/dist/db/createBundleDiff.d.mts +3 -3
- package/dist/db/createBundleDiff.mjs +47 -68
- package/dist/db/databasePluginCore.cjs +138 -0
- package/dist/db/databasePluginCore.mjs +137 -0
- package/dist/db/fixedMigrator.cjs +36 -93
- package/dist/db/fixedMigrator.mjs +36 -93
- package/dist/db/fixedMigratorMongo.cjs +319 -0
- package/dist/db/fixedMigratorMongo.mjs +319 -0
- package/dist/db/fixedMigratorShared.cjs +23 -0
- package/dist/db/fixedMigratorShared.mjs +18 -0
- package/dist/db/mongoMigrationExecution.cjs +11 -0
- package/dist/db/mongoMigrationExecution.mjs +11 -0
- package/dist/db/releaseCatalog.cjs +66 -0
- package/dist/db/releaseCatalog.d.cts +17 -0
- package/dist/db/releaseCatalog.d.mts +17 -0
- package/dist/db/releaseCatalog.mjs +64 -0
- package/dist/db/schema/mongodb.cjs +14 -6
- package/dist/db/schema/mongodb.mjs +14 -6
- package/dist/db/schema/registry.cjs +0 -2
- package/dist/db/schema/registry.mjs +1 -2
- package/dist/db/schema/sql.cjs +31 -14
- package/dist/db/schema/sql.mjs +32 -13
- package/dist/db/schema/sqlOperations.cjs +4 -4
- package/dist/db/schema/sqlOperations.mjs +5 -5
- package/dist/db/schemaGenerators.cjs +52 -18
- package/dist/db/schemaGenerators.mjs +52 -18
- package/dist/db/schemaReadiness.cjs +4 -3
- package/dist/db/schemaReadiness.mjs +4 -3
- package/dist/db/sqlMigrationExecution.cjs +67 -0
- package/dist/db/sqlMigrationExecution.mjs +66 -0
- package/dist/db/types.cjs +2 -6
- package/dist/db/types.d.cts +60 -16
- package/dist/db/types.d.mts +60 -16
- package/dist/db/types.mjs +3 -6
- package/dist/db/updateArtifacts.cjs +114 -60
- package/dist/db/updateArtifacts.mjs +117 -62
- package/dist/handler.cjs +108 -282
- package/dist/handler.d.cts +3 -46
- package/dist/handler.d.mts +3 -46
- package/dist/handler.mjs +107 -281
- package/dist/handlerBundleRoutes.cjs +155 -0
- package/dist/handlerBundleRoutes.mjs +155 -0
- package/dist/handlerErrors.cjs +9 -0
- package/dist/handlerErrors.mjs +9 -0
- package/dist/handlerParameters.cjs +39 -0
- package/dist/handlerParameters.mjs +34 -0
- package/dist/handlerReleaseCatalogRoutes.cjs +111 -0
- package/dist/handlerReleaseCatalogRoutes.mjs +110 -0
- package/dist/handlerReleaseManagementRoutes.cjs +125 -0
- package/dist/handlerReleaseManagementRoutes.mjs +124 -0
- package/dist/handlerTypes.d.cts +63 -0
- package/dist/handlerTypes.d.mts +63 -0
- package/dist/handlerVersionRoutes.cjs +10 -0
- package/dist/handlerVersionRoutes.d.cts +4 -0
- package/dist/handlerVersionRoutes.d.mts +4 -0
- package/dist/handlerVersionRoutes.mjs +9 -0
- package/dist/index.cjs +10 -1
- package/dist/index.d.cts +9 -3
- package/dist/index.d.mts +9 -3
- package/dist/index.mjs +5 -2
- package/dist/insights/bounded/activeOverview.cjs +87 -0
- package/dist/insights/bounded/activeOverview.mjs +85 -0
- package/dist/insights/bounded/installationSearch.cjs +42 -0
- package/dist/insights/bounded/installationSearch.mjs +42 -0
- package/dist/insights/bounded/persistence.cjs +39 -0
- package/dist/insights/bounded/persistence.mjs +38 -0
- package/dist/insights/bounded/provider.cjs +170 -0
- package/dist/insights/bounded/provider.d.cts +7 -0
- package/dist/insights/bounded/provider.d.mts +7 -0
- package/dist/insights/bounded/provider.mjs +170 -0
- package/dist/insights/bounded/scan.cjs +144 -0
- package/dist/insights/bounded/scan.mjs +138 -0
- package/dist/insights/domain.d.cts +121 -0
- package/dist/insights/domain.d.mts +121 -0
- package/dist/insights/errors.cjs +22 -0
- package/dist/insights/errors.mjs +20 -0
- package/dist/insights/eventInput.cjs +111 -0
- package/dist/insights/eventInput.mjs +111 -0
- package/dist/insights/persistence.d.cts +6 -0
- package/dist/insights/persistence.d.mts +6 -0
- package/dist/insights/queryInput.cjs +58 -0
- package/dist/insights/queryInput.mjs +55 -0
- package/dist/insights/routes.cjs +63 -0
- package/dist/insights/routes.mjs +61 -0
- package/dist/insights/types.d.cts +17 -0
- package/dist/insights/types.d.mts +17 -0
- package/dist/node.cjs +19 -12
- package/dist/node.d.cts +15 -10
- package/dist/node.d.mts +15 -10
- package/dist/node.mjs +19 -13
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/schema/dsl.cjs +19 -2
- package/dist/schema/dsl.mjs +17 -3
- package/dist/schema/index.cjs +1 -8
- package/dist/schema/index.mjs +2 -8
- package/dist/schema/types.cjs +3 -1
- package/dist/schema/types.mjs +3 -2
- package/dist/schema/v1_0_0.cjs +364 -0
- package/dist/schema/v1_0_0.mjs +357 -0
- package/dist/storageAccess.cjs +69 -25
- package/dist/storageAccess.mjs +68 -25
- package/dist/types/index.d.cts +4 -3
- package/dist/types/index.d.mts +5 -4
- package/package.json +8 -7
- package/src/adapters/databaseConstraintErrors.ts +25 -0
- package/src/adapters/databasePluginTestDatabase.ts +106 -0
- package/src/adapters/databasePluginUtils.spec.ts +29 -0
- package/src/adapters/databasePluginUtils.ts +214 -0
- package/src/adapters/drizzle.spec.ts +208 -47
- package/src/adapters/drizzle.ts +106 -293
- package/src/adapters/drizzleCrud.ts +419 -0
- package/src/adapters/drizzleLazyDB.ts +206 -100
- package/src/adapters/drizzleQuery.ts +164 -0
- package/src/adapters/kysely.spec.ts +126 -88
- package/src/adapters/kysely.ts +76 -334
- package/src/adapters/kyselyCrud.ts +588 -0
- package/src/adapters/kyselyQuery.ts +128 -0
- package/src/adapters/mongodb.spec.ts +141 -0
- package/src/adapters/mongodb.ts +60 -288
- package/src/adapters/mongodbCollections.ts +62 -0
- package/src/adapters/mongodbQuery.ts +226 -0
- package/src/adapters/mongodbReads.ts +311 -0
- package/src/adapters/mongodbRows.spec.ts +64 -0
- package/src/adapters/mongodbRows.ts +118 -0
- package/src/adapters/mongodbTestClient.ts +342 -0
- package/src/adapters/mongodbTestFilter.ts +166 -0
- package/src/adapters/mongodbWrites.ts +260 -0
- package/src/adapters/prisma.spec.ts +196 -0
- package/src/adapters/prisma.ts +417 -292
- package/src/adapters/prismaQuery.ts +129 -0
- package/src/adapters/prismaRows.spec.ts +98 -0
- package/src/adapters/prismaRows.ts +301 -0
- package/src/adapters/prismaTestClient.ts +396 -0
- package/src/apiKeys.integration.spec.ts +192 -0
- package/src/apiKeys.spec.ts +162 -0
- package/src/apiKeys.ts +220 -0
- package/src/createHotUpdaterCore.ts +164 -100
- package/src/db/bundleRows.spec.ts +164 -0
- package/src/db/bundleRows.ts +10 -140
- package/src/db/createBundleDiff.spec.ts +172 -127
- package/src/db/createBundleDiff.ts +91 -110
- package/src/db/databasePluginCore.spec.ts +62 -0
- package/src/db/databasePluginCore.testFixtures.ts +76 -0
- package/src/db/databasePluginCore.ts +185 -0
- package/src/db/databasePluginUpdateCheck.bench.ts +107 -0
- package/src/db/fixedMigrator.spec.ts +184 -42
- package/src/db/fixedMigrator.ts +64 -205
- package/src/db/fixedMigratorMongo.spec.ts +192 -0
- package/src/db/fixedMigratorMongo.ts +425 -0
- package/src/db/fixedMigratorShared.ts +36 -0
- package/src/db/hotUpdaterSchema.ts +0 -1
- package/src/db/index.spec.ts +333 -1025
- package/src/db/mongoMigrationExecution.ts +23 -0
- package/src/db/node-sqlite.d.ts +25 -0
- package/src/db/releaseCatalog.ts +148 -0
- package/src/db/schema/mongodb.ts +20 -6
- package/src/db/schema/sql.ts +52 -11
- package/src/db/schema/sqlOperations.ts +5 -4
- package/src/db/schemaGenerators.ts +107 -23
- package/src/db/schemaReadiness.ts +2 -1
- package/src/db/sqlMigrationExecution.spec.ts +65 -0
- package/src/db/sqlMigrationExecution.ts +108 -0
- package/src/db/types.spec.ts +55 -0
- package/src/db/types.ts +129 -65
- package/src/db/updateArtifacts.spec.ts +359 -0
- package/src/db/updateArtifacts.ts +290 -126
- package/src/handler-standalone.integration.spec.ts +401 -364
- package/src/handler.spec.ts +137 -582
- package/src/handler.testFixtures.ts +30 -0
- package/src/handler.ts +218 -616
- package/src/handlerAuth.integration.spec.ts +33 -0
- package/src/handlerBundleRoutes.ts +259 -0
- package/src/handlerErrors.ts +7 -0
- package/src/handlerManagement.spec.ts +262 -0
- package/src/handlerParameters.ts +105 -0
- package/src/handlerReleaseCatalogRoutes.ts +154 -0
- package/src/handlerReleaseManagementRoutes.ts +213 -0
- package/src/handlerTypes.ts +96 -0
- package/src/handlerVersionRoutes.ts +12 -0
- package/src/index.ts +22 -2
- package/src/insights/bounded/activeOverview.spec.ts +189 -0
- package/src/insights/bounded/activeOverview.ts +148 -0
- package/src/insights/bounded/installationSearch.spec.ts +130 -0
- package/src/insights/bounded/installationSearch.ts +69 -0
- package/src/insights/bounded/persistence.ts +43 -0
- package/src/insights/bounded/provider.spec.ts +219 -0
- package/src/insights/bounded/provider.ts +229 -0
- package/src/insights/bounded/scan.ts +235 -0
- package/src/insights/domain.ts +139 -0
- package/src/insights/errors.ts +18 -0
- package/src/insights/eventInput.ts +168 -0
- package/src/insights/insights.integration.spec.ts +144 -0
- package/src/insights/persistence.ts +10 -0
- package/src/insights/queryInput.ts +102 -0
- package/src/insights/routes.ts +123 -0
- package/src/insights/types.ts +44 -0
- package/src/node.spec.ts +79 -10
- package/src/node.ts +20 -17
- package/src/releaseCatalogRoutes.integration.spec.ts +238 -0
- package/src/runtime.spec.ts +126 -959
- package/src/runtime.testFixtures.ts +71 -0
- package/src/schema/dsl-all-versions.spec.ts +26 -17
- package/src/schema/dsl.ts +41 -2
- package/src/schema/index.ts +3 -9
- package/src/schema/types.ts +9 -3
- package/src/schema/v1_0_0.spec.ts +146 -0
- package/src/schema/v1_0_0.ts +465 -0
- package/src/storageAccess.spec.ts +127 -32
- package/src/storageAccess.ts +106 -55
- package/src/types/index.ts +2 -1
- package/dist/db/bundleRows.cjs +0 -82
- package/dist/db/bundleRows.d.cts +0 -33
- package/dist/db/bundleRows.d.mts +0 -33
- package/dist/db/bundleRows.mjs +0 -79
- package/dist/db/pluginCore.cjs +0 -242
- package/dist/db/pluginCore.mjs +0 -241
- package/dist/db/schema/sqlMigrations.cjs +0 -74
- package/dist/db/schema/sqlMigrations.mjs +0 -73
- package/dist/db/schemaEnhancements.cjs +0 -20
- package/dist/db/schemaEnhancements.mjs +0 -19
- package/dist/route.cjs +0 -7
- package/dist/route.mjs +0 -7
- package/dist/schema/v0_21_0.cjs +0 -38
- package/dist/schema/v0_21_0.mjs +0 -37
- package/dist/schema/v0_29_0.cjs +0 -45
- package/dist/schema/v0_29_0.mjs +0 -44
- package/dist/schema/v0_31_0.cjs +0 -82
- package/dist/schema/v0_31_0.mjs +0 -80
- package/src/db/pluginCore.spec.ts +0 -1095
- package/src/db/pluginCore.ts +0 -475
- package/src/db/pluginUpdateCheck.bench.ts +0 -254
- package/src/db/schema/sqlMigrations.ts +0 -219
- package/src/db/schemaEnhancements.ts +0 -55
- package/src/route.ts +0 -7
- package/src/schema/v0_21_0.ts +0 -52
- package/src/schema/v0_29_0.ts +0 -62
- package/src/schema/v0_31_0.spec.ts +0 -73
- package/src/schema/v0_31_0.ts +0 -120
package/dist/apiKeys.mjs
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
//#region src/apiKeys.ts
|
|
2
|
+
const API_KEY_HEADER_NAME = "x-api-key";
|
|
3
|
+
const BASE64URL_32_PATTERN = /^[A-Za-z0-9_-]{43}$/u;
|
|
4
|
+
const bytesToBase64Url = (bytes) => {
|
|
5
|
+
let binary = "";
|
|
6
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
7
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
|
|
8
|
+
};
|
|
9
|
+
const isApiKey = (value) => {
|
|
10
|
+
if (!BASE64URL_32_PATTERN.test(value)) return false;
|
|
11
|
+
try {
|
|
12
|
+
const base64 = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
13
|
+
const binary = atob(`${base64}=`);
|
|
14
|
+
const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
15
|
+
return bytes.byteLength === 32 && bytesToBase64Url(bytes) === value;
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const normalizeApiKeyHeaderName = (value = API_KEY_HEADER_NAME) => {
|
|
21
|
+
if (typeof value !== "string" || value.length === 0) throw new TypeError("clientAccess.headerName must be a valid header name.");
|
|
22
|
+
try {
|
|
23
|
+
const headers = new Headers();
|
|
24
|
+
headers.set(value, "");
|
|
25
|
+
return headers.keys().next().value;
|
|
26
|
+
} catch {
|
|
27
|
+
throw new TypeError("clientAccess.headerName must be a valid header name.");
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const hashApiKey = async (apiKey) => {
|
|
31
|
+
if (!isApiKey(apiKey)) throw new TypeError("API key must be a canonical 32-byte base64url value.");
|
|
32
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(apiKey));
|
|
33
|
+
return bytesToBase64Url(new Uint8Array(digest));
|
|
34
|
+
};
|
|
35
|
+
const apiKeyId = () => {
|
|
36
|
+
const bytes = new Uint8Array(16);
|
|
37
|
+
crypto.getRandomValues(bytes);
|
|
38
|
+
return `api-${bytesToBase64Url(bytes)}`;
|
|
39
|
+
};
|
|
40
|
+
const normalizeApiKeyName = (name) => {
|
|
41
|
+
const normalized = name.trim();
|
|
42
|
+
if (normalized.length === 0 || normalized.length > 64 || /[\p{Cc}\p{Cf}]/u.test(normalized)) throw new TypeError("API key names must contain 1-64 visible characters.");
|
|
43
|
+
return normalized;
|
|
44
|
+
};
|
|
45
|
+
const toApiKeyMetadata = ({ hash: _hash, ...metadata }) => Object.freeze(metadata);
|
|
46
|
+
const registerApiKey = async (input) => {
|
|
47
|
+
const hash = await hashApiKey(input.apiKey);
|
|
48
|
+
const createdAtMs = input.createdAtMs ?? Date.now();
|
|
49
|
+
if (!Number.isSafeInteger(createdAtMs) || createdAtMs < 0) throw new TypeError("API key creation time must be non-negative.");
|
|
50
|
+
const record = Object.freeze({
|
|
51
|
+
created_at_ms: createdAtMs,
|
|
52
|
+
hash,
|
|
53
|
+
id: apiKeyId(),
|
|
54
|
+
name: normalizeApiKeyName(input.name),
|
|
55
|
+
prefix: input.apiKey.slice(0, 6),
|
|
56
|
+
revoked_at_ms: null,
|
|
57
|
+
role: "client"
|
|
58
|
+
});
|
|
59
|
+
if (await input.apiKeys.create(record) === "existing") {
|
|
60
|
+
const existing = await input.apiKeys.findByHash(hash);
|
|
61
|
+
if (existing === null || existing.revoked_at_ms !== null) throw new Error("The API key has been revoked.");
|
|
62
|
+
return Object.freeze({
|
|
63
|
+
apiKey: input.apiKey,
|
|
64
|
+
record: toApiKeyMetadata(existing)
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return Object.freeze({
|
|
68
|
+
apiKey: input.apiKey,
|
|
69
|
+
record: toApiKeyMetadata(record)
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
const createApiKey = (input) => {
|
|
73
|
+
const bytes = new Uint8Array(32);
|
|
74
|
+
crypto.getRandomValues(bytes);
|
|
75
|
+
return registerApiKey({
|
|
76
|
+
apiKey: bytesToBase64Url(bytes),
|
|
77
|
+
apiKeys: input.apiKeys,
|
|
78
|
+
name: input.name
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Provisions the client API key used by managed init.
|
|
83
|
+
*
|
|
84
|
+
* A saved plaintext key is registered idempotently; otherwise a new key is
|
|
85
|
+
* created. Callers must persist or display the returned plaintext because the
|
|
86
|
+
* database stores only its hash.
|
|
87
|
+
*/
|
|
88
|
+
const provisionApiKey = (input) => {
|
|
89
|
+
const existingApiKey = input.existingApiKey?.trim();
|
|
90
|
+
return existingApiKey ? registerApiKey({
|
|
91
|
+
apiKey: existingApiKey,
|
|
92
|
+
apiKeys: input.apiKeys,
|
|
93
|
+
name: input.name
|
|
94
|
+
}) : createApiKey({
|
|
95
|
+
apiKeys: input.apiKeys,
|
|
96
|
+
name: input.name
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
const authenticateApiKey = async (input) => {
|
|
100
|
+
const apiKey = input.request.headers.get(normalizeApiKeyHeaderName(input.headerName));
|
|
101
|
+
if (apiKey === null || !isApiKey(apiKey)) return false;
|
|
102
|
+
const hash = await hashApiKey(apiKey);
|
|
103
|
+
await input.beforeLookup?.();
|
|
104
|
+
const record = await input.apiKeys.findByHash(hash);
|
|
105
|
+
return record !== null && record.hash === hash && record.role === "client" && record.revoked_at_ms === null;
|
|
106
|
+
};
|
|
107
|
+
const createApiKeyManagement = (input) => Object.freeze({
|
|
108
|
+
async create({ name }) {
|
|
109
|
+
await input.beforeOperation();
|
|
110
|
+
const created = await createApiKey({
|
|
111
|
+
apiKeys: input.apiKeys,
|
|
112
|
+
name
|
|
113
|
+
});
|
|
114
|
+
return Object.freeze({
|
|
115
|
+
apiKey: created.apiKey,
|
|
116
|
+
record: created.record
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
async list() {
|
|
120
|
+
await input.beforeOperation();
|
|
121
|
+
return Object.freeze((await input.apiKeys.list()).map(toApiKeyMetadata));
|
|
122
|
+
},
|
|
123
|
+
async revoke({ id }) {
|
|
124
|
+
await input.beforeOperation();
|
|
125
|
+
const record = await input.apiKeys.revoke({
|
|
126
|
+
id,
|
|
127
|
+
revokedAtMs: Date.now()
|
|
128
|
+
});
|
|
129
|
+
return record === null ? null : toApiKeyMetadata(record);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
//#endregion
|
|
133
|
+
export { API_KEY_HEADER_NAME, authenticateApiKey, createApiKey, createApiKeyManagement, normalizeApiKeyHeaderName, provisionApiKey, registerApiKey };
|
|
@@ -1,50 +1,72 @@
|
|
|
1
1
|
require("./_virtual/_rolldown/runtime.cjs");
|
|
2
2
|
const require_handler = require("./handler.cjs");
|
|
3
|
-
const
|
|
3
|
+
const require_provider = require("./insights/bounded/provider.cjs");
|
|
4
|
+
const require_apiKeys = require("./apiKeys.cjs");
|
|
5
|
+
const require_databasePluginCore = require("./db/databasePluginCore.cjs");
|
|
4
6
|
const require_schemaReadiness = require("./db/schemaReadiness.cjs");
|
|
5
7
|
const require_types = require("./db/types.cjs");
|
|
6
|
-
const require_route = require("./route.cjs");
|
|
7
8
|
const require_storageAccess = require("./storageAccess.cjs");
|
|
8
9
|
let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
|
|
9
10
|
//#region src/createHotUpdaterCore.ts
|
|
11
|
+
const normalizeClientAccess = (value) => {
|
|
12
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new TypeError("clientAccess must be an object.");
|
|
13
|
+
const policy = value;
|
|
14
|
+
if (policy.type === "public") return { type: "public" };
|
|
15
|
+
if (policy.type === "api-key") return {
|
|
16
|
+
headerName: require_apiKeys.normalizeApiKeyHeaderName(policy.headerName),
|
|
17
|
+
type: "api-key"
|
|
18
|
+
};
|
|
19
|
+
throw new TypeError("clientAccess.type must be either \"public\" or \"api-key\".");
|
|
20
|
+
};
|
|
10
21
|
const hotUpdaterCoreMetadata = Symbol.for("@hot-updater/server/core-metadata");
|
|
11
22
|
function getHotUpdaterCoreMetadata(hotUpdater) {
|
|
12
23
|
return hotUpdater[hotUpdaterCoreMetadata];
|
|
13
24
|
}
|
|
14
25
|
function createHotUpdaterCore(options) {
|
|
26
|
+
for (const key of ["authorityId", "catalogId"]) if (Object.hasOwn(options, key)) throw new TypeError(`Remove ${key} from createHotUpdater options. Catalog identity is managed internally.`);
|
|
15
27
|
const database = options.database;
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
(0, _hot_updater_plugin_core.assertRuntimeStoragePlugin)(storagePlugin);
|
|
20
|
-
return storagePlugin;
|
|
28
|
+
const { downloadStorageObject, readStorageText, resolveFileUrl } = require_storageAccess.createStorageAccess((options.storage ?? []).map((storage) => {
|
|
29
|
+
(0, _hot_updater_plugin_core.assertStorageOperations)(storage, ["get", "getDownloadUrl"]);
|
|
30
|
+
return storage;
|
|
21
31
|
}));
|
|
22
|
-
if (!require_types.isDatabasePluginFactory(database) && !require_types.isDatabasePlugin(database)) throw new Error("@hot-updater/server only supports database plugins.");
|
|
23
32
|
const adapterCapabilities = database;
|
|
24
|
-
|
|
33
|
+
if (!require_types.isDatabasePlugin(database)) throw new Error("@hot-updater/server only supports database plugins.");
|
|
34
|
+
const plugin = database;
|
|
25
35
|
const assertSchemaReady = require_schemaReadiness.createSchemaReadinessChecker(adapterCapabilities.adapterName ?? plugin.name, adapterCapabilities.createMigrator);
|
|
26
|
-
const core =
|
|
27
|
-
createMutationPlugin: () => database(),
|
|
28
|
-
beforeOperation: assertSchemaReady,
|
|
29
|
-
readStorageText
|
|
30
|
-
} : {
|
|
36
|
+
const core = require_databasePluginCore.createDatabasePluginCore(plugin, resolveFileUrl, {
|
|
31
37
|
beforeOperation: assertSchemaReady,
|
|
32
38
|
readStorageText
|
|
33
39
|
});
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
const clientAccess = normalizeClientAccess(options.clientAccess);
|
|
41
|
+
const insights = require_provider.createInsightsProvider({
|
|
42
|
+
async append(row) {
|
|
43
|
+
await assertSchemaReady();
|
|
44
|
+
return plugin.models.insights.append(row);
|
|
45
|
+
},
|
|
46
|
+
async scan(input) {
|
|
47
|
+
await assertSchemaReady();
|
|
48
|
+
return plugin.models.insights.scan(input);
|
|
49
|
+
}
|
|
37
50
|
});
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
};
|
|
42
|
-
const
|
|
43
|
-
|
|
51
|
+
const apiKeys = require_apiKeys.createApiKeyManagement({
|
|
52
|
+
apiKeys: plugin.models.apiKeys,
|
|
53
|
+
beforeOperation: assertSchemaReady
|
|
54
|
+
});
|
|
55
|
+
const handlers = require_handler.createHotUpdaterHandlers(core.api, insights, clientAccess.type === "api-key" ? {
|
|
56
|
+
authenticate: (request) => require_apiKeys.authenticateApiKey({
|
|
57
|
+
apiKeys: plugin.models.apiKeys,
|
|
58
|
+
beforeLookup: assertSchemaReady,
|
|
59
|
+
headerName: clientAccess.headerName,
|
|
60
|
+
request
|
|
61
|
+
}),
|
|
62
|
+
headerName: clientAccess.headerName
|
|
63
|
+
} : void 0, downloadStorageObject);
|
|
64
|
+
const api = Object.assign({
|
|
44
65
|
adapterName: adapterCapabilities.adapterName ?? core.adapterName,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
66
|
+
insights,
|
|
67
|
+
apiKeys,
|
|
68
|
+
handlers
|
|
69
|
+
}, core.api);
|
|
48
70
|
Object.defineProperty(api, hotUpdaterCoreMetadata, {
|
|
49
71
|
enumerable: false,
|
|
50
72
|
value: {
|
|
@@ -1,25 +1,57 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { ApiKeyManagementAPI } from "./apiKeys.cjs";
|
|
2
|
+
import { HotUpdaterHandlers } from "./handlerTypes.cjs";
|
|
3
|
+
import { InsightsProvider } from "./insights/types.cjs";
|
|
4
|
+
import { DatabaseAPI, DatabasePlugin as DatabasePlugin$1 } from "./db/types.cjs";
|
|
5
|
+
import { StoragePlugin } from "@hot-updater/plugin-core";
|
|
4
6
|
|
|
5
7
|
//#region src/createHotUpdaterCore.d.ts
|
|
6
|
-
type RuntimeHotUpdaterAPI
|
|
7
|
-
readonly
|
|
8
|
-
readonly handler: (request: Request, context?: HotUpdaterContext<TContext>) => Promise<Response>;
|
|
8
|
+
type RuntimeHotUpdaterAPI = DatabaseAPI & {
|
|
9
|
+
readonly handlers: HotUpdaterHandlers;
|
|
9
10
|
readonly adapterName: string;
|
|
11
|
+
/**
|
|
12
|
+
* Built-in Insights provider. Client ingestion and admin query routes are
|
|
13
|
+
* always mounted; React Native clients report lifecycle events by default
|
|
14
|
+
* and can opt out with `HotUpdater.init({ insights: false })`.
|
|
15
|
+
*/
|
|
16
|
+
readonly insights: InsightsProvider;
|
|
17
|
+
/**
|
|
18
|
+
* In-process API key lifecycle operations for trusted server tooling.
|
|
19
|
+
* Creation returns the plaintext once; list and revoke expose only metadata.
|
|
20
|
+
* This capability is never mounted on the client or admin HTTP handlers.
|
|
21
|
+
*/
|
|
22
|
+
readonly apiKeys: ApiKeyManagementAPI;
|
|
23
|
+
};
|
|
24
|
+
type HotUpdaterAPI = RuntimeHotUpdaterAPI;
|
|
25
|
+
type ClientAccessPolicy = {
|
|
26
|
+
/**
|
|
27
|
+
* Leaves Release Catalog, artifact, and Insights ingestion routes
|
|
28
|
+
* publicly accessible without a client credential.
|
|
29
|
+
*/
|
|
30
|
+
readonly type: "public";
|
|
31
|
+
} | {
|
|
32
|
+
/**
|
|
33
|
+
* Requires a key registered in `database.models.apiKeys` for
|
|
34
|
+
* Release Catalog, artifact, and Insights ingestion requests.
|
|
35
|
+
*/
|
|
36
|
+
readonly type: "api-key";
|
|
37
|
+
/**
|
|
38
|
+
* Request header containing the API key. Defaults to
|
|
39
|
+
* `x-api-key`. Clients must send the same header; Release Catalog
|
|
40
|
+
* responses include it in `Vary` to preserve cache isolation.
|
|
41
|
+
*/
|
|
42
|
+
readonly headerName?: string;
|
|
10
43
|
};
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
readonly database: DatabaseAdapter<TContext>;
|
|
14
|
-
readonly storages?: readonly (RuntimeStoragePlugin<TContext> | StoragePluginFactory<TContext>)[];
|
|
44
|
+
interface CreateHotUpdaterOptions {
|
|
45
|
+
readonly database: DatabasePlugin$1;
|
|
15
46
|
/**
|
|
16
|
-
*
|
|
47
|
+
* Required client-route access policy. This choice is explicit so a server
|
|
48
|
+
* cannot accidentally change between public and authenticated OTA access.
|
|
49
|
+
* `/version`, storage downloads, and admin-handler routes are unaffected.
|
|
17
50
|
*/
|
|
18
|
-
readonly
|
|
19
|
-
|
|
20
|
-
readonly
|
|
21
|
-
readonly routes?: HandlerRoutes;
|
|
51
|
+
readonly clientAccess: ClientAccessPolicy;
|
|
52
|
+
/** Storage implementations used to read provider-specific storage URIs. */
|
|
53
|
+
readonly storage?: readonly StoragePlugin[];
|
|
22
54
|
}
|
|
23
|
-
declare function createHotUpdater
|
|
55
|
+
declare function createHotUpdater(options: CreateHotUpdaterOptions): RuntimeHotUpdaterAPI;
|
|
24
56
|
//#endregion
|
|
25
|
-
export { CreateHotUpdaterOptions, HotUpdaterAPI, RuntimeHotUpdaterAPI, createHotUpdater };
|
|
57
|
+
export { ClientAccessPolicy, CreateHotUpdaterOptions, HotUpdaterAPI, RuntimeHotUpdaterAPI, createHotUpdater };
|
|
@@ -1,25 +1,57 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { ApiKeyManagementAPI } from "./apiKeys.mjs";
|
|
2
|
+
import { HotUpdaterHandlers } from "./handlerTypes.mjs";
|
|
3
|
+
import { InsightsProvider } from "./insights/types.mjs";
|
|
4
|
+
import { DatabaseAPI, DatabasePlugin as DatabasePlugin$1 } from "./db/types.mjs";
|
|
5
|
+
import { StoragePlugin } from "@hot-updater/plugin-core";
|
|
4
6
|
|
|
5
7
|
//#region src/createHotUpdaterCore.d.ts
|
|
6
|
-
type RuntimeHotUpdaterAPI
|
|
7
|
-
readonly
|
|
8
|
-
readonly handler: (request: Request, context?: HotUpdaterContext<TContext>) => Promise<Response>;
|
|
8
|
+
type RuntimeHotUpdaterAPI = DatabaseAPI & {
|
|
9
|
+
readonly handlers: HotUpdaterHandlers;
|
|
9
10
|
readonly adapterName: string;
|
|
11
|
+
/**
|
|
12
|
+
* Built-in Insights provider. Client ingestion and admin query routes are
|
|
13
|
+
* always mounted; React Native clients report lifecycle events by default
|
|
14
|
+
* and can opt out with `HotUpdater.init({ insights: false })`.
|
|
15
|
+
*/
|
|
16
|
+
readonly insights: InsightsProvider;
|
|
17
|
+
/**
|
|
18
|
+
* In-process API key lifecycle operations for trusted server tooling.
|
|
19
|
+
* Creation returns the plaintext once; list and revoke expose only metadata.
|
|
20
|
+
* This capability is never mounted on the client or admin HTTP handlers.
|
|
21
|
+
*/
|
|
22
|
+
readonly apiKeys: ApiKeyManagementAPI;
|
|
23
|
+
};
|
|
24
|
+
type HotUpdaterAPI = RuntimeHotUpdaterAPI;
|
|
25
|
+
type ClientAccessPolicy = {
|
|
26
|
+
/**
|
|
27
|
+
* Leaves Release Catalog, artifact, and Insights ingestion routes
|
|
28
|
+
* publicly accessible without a client credential.
|
|
29
|
+
*/
|
|
30
|
+
readonly type: "public";
|
|
31
|
+
} | {
|
|
32
|
+
/**
|
|
33
|
+
* Requires a key registered in `database.models.apiKeys` for
|
|
34
|
+
* Release Catalog, artifact, and Insights ingestion requests.
|
|
35
|
+
*/
|
|
36
|
+
readonly type: "api-key";
|
|
37
|
+
/**
|
|
38
|
+
* Request header containing the API key. Defaults to
|
|
39
|
+
* `x-api-key`. Clients must send the same header; Release Catalog
|
|
40
|
+
* responses include it in `Vary` to preserve cache isolation.
|
|
41
|
+
*/
|
|
42
|
+
readonly headerName?: string;
|
|
10
43
|
};
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
readonly database: DatabaseAdapter<TContext>;
|
|
14
|
-
readonly storages?: readonly (RuntimeStoragePlugin<TContext> | StoragePluginFactory<TContext>)[];
|
|
44
|
+
interface CreateHotUpdaterOptions {
|
|
45
|
+
readonly database: DatabasePlugin$1;
|
|
15
46
|
/**
|
|
16
|
-
*
|
|
47
|
+
* Required client-route access policy. This choice is explicit so a server
|
|
48
|
+
* cannot accidentally change between public and authenticated OTA access.
|
|
49
|
+
* `/version`, storage downloads, and admin-handler routes are unaffected.
|
|
17
50
|
*/
|
|
18
|
-
readonly
|
|
19
|
-
|
|
20
|
-
readonly
|
|
21
|
-
readonly routes?: HandlerRoutes;
|
|
51
|
+
readonly clientAccess: ClientAccessPolicy;
|
|
52
|
+
/** Storage implementations used to read provider-specific storage URIs. */
|
|
53
|
+
readonly storage?: readonly StoragePlugin[];
|
|
22
54
|
}
|
|
23
|
-
declare function createHotUpdater
|
|
55
|
+
declare function createHotUpdater(options: CreateHotUpdaterOptions): RuntimeHotUpdaterAPI;
|
|
24
56
|
//#endregion
|
|
25
|
-
export { CreateHotUpdaterOptions, HotUpdaterAPI, RuntimeHotUpdaterAPI, createHotUpdater };
|
|
57
|
+
export { ClientAccessPolicy, CreateHotUpdaterOptions, HotUpdaterAPI, RuntimeHotUpdaterAPI, createHotUpdater };
|
|
@@ -1,49 +1,71 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { createHotUpdaterHandlers } from "./handler.mjs";
|
|
2
|
+
import { createInsightsProvider } from "./insights/bounded/provider.mjs";
|
|
3
|
+
import { authenticateApiKey, createApiKeyManagement, normalizeApiKeyHeaderName } from "./apiKeys.mjs";
|
|
4
|
+
import { createDatabasePluginCore } from "./db/databasePluginCore.mjs";
|
|
3
5
|
import { createSchemaReadinessChecker } from "./db/schemaReadiness.mjs";
|
|
4
|
-
import { isDatabasePlugin
|
|
5
|
-
import { normalizeBasePath } from "./route.mjs";
|
|
6
|
+
import { isDatabasePlugin } from "./db/types.mjs";
|
|
6
7
|
import { createStorageAccess } from "./storageAccess.mjs";
|
|
7
|
-
import {
|
|
8
|
+
import { assertStorageOperations } from "@hot-updater/plugin-core";
|
|
8
9
|
//#region src/createHotUpdaterCore.ts
|
|
10
|
+
const normalizeClientAccess = (value) => {
|
|
11
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new TypeError("clientAccess must be an object.");
|
|
12
|
+
const policy = value;
|
|
13
|
+
if (policy.type === "public") return { type: "public" };
|
|
14
|
+
if (policy.type === "api-key") return {
|
|
15
|
+
headerName: normalizeApiKeyHeaderName(policy.headerName),
|
|
16
|
+
type: "api-key"
|
|
17
|
+
};
|
|
18
|
+
throw new TypeError("clientAccess.type must be either \"public\" or \"api-key\".");
|
|
19
|
+
};
|
|
9
20
|
const hotUpdaterCoreMetadata = Symbol.for("@hot-updater/server/core-metadata");
|
|
10
21
|
function getHotUpdaterCoreMetadata(hotUpdater) {
|
|
11
22
|
return hotUpdater[hotUpdaterCoreMetadata];
|
|
12
23
|
}
|
|
13
24
|
function createHotUpdaterCore(options) {
|
|
25
|
+
for (const key of ["authorityId", "catalogId"]) if (Object.hasOwn(options, key)) throw new TypeError(`Remove ${key} from createHotUpdater options. Catalog identity is managed internally.`);
|
|
14
26
|
const database = options.database;
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
assertRuntimeStoragePlugin(storagePlugin);
|
|
19
|
-
return storagePlugin;
|
|
27
|
+
const { downloadStorageObject, readStorageText, resolveFileUrl } = createStorageAccess((options.storage ?? []).map((storage) => {
|
|
28
|
+
assertStorageOperations(storage, ["get", "getDownloadUrl"]);
|
|
29
|
+
return storage;
|
|
20
30
|
}));
|
|
21
|
-
if (!isDatabasePluginFactory(database) && !isDatabasePlugin(database)) throw new Error("@hot-updater/server only supports database plugins.");
|
|
22
31
|
const adapterCapabilities = database;
|
|
23
|
-
|
|
32
|
+
if (!isDatabasePlugin(database)) throw new Error("@hot-updater/server only supports database plugins.");
|
|
33
|
+
const plugin = database;
|
|
24
34
|
const assertSchemaReady = createSchemaReadinessChecker(adapterCapabilities.adapterName ?? plugin.name, adapterCapabilities.createMigrator);
|
|
25
|
-
const core =
|
|
26
|
-
createMutationPlugin: () => database(),
|
|
27
|
-
beforeOperation: assertSchemaReady,
|
|
28
|
-
readStorageText
|
|
29
|
-
} : {
|
|
35
|
+
const core = createDatabasePluginCore(plugin, resolveFileUrl, {
|
|
30
36
|
beforeOperation: assertSchemaReady,
|
|
31
37
|
readStorageText
|
|
32
38
|
});
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
const clientAccess = normalizeClientAccess(options.clientAccess);
|
|
40
|
+
const insights = createInsightsProvider({
|
|
41
|
+
async append(row) {
|
|
42
|
+
await assertSchemaReady();
|
|
43
|
+
return plugin.models.insights.append(row);
|
|
44
|
+
},
|
|
45
|
+
async scan(input) {
|
|
46
|
+
await assertSchemaReady();
|
|
47
|
+
return plugin.models.insights.scan(input);
|
|
48
|
+
}
|
|
36
49
|
});
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
};
|
|
41
|
-
const
|
|
42
|
-
|
|
50
|
+
const apiKeys = createApiKeyManagement({
|
|
51
|
+
apiKeys: plugin.models.apiKeys,
|
|
52
|
+
beforeOperation: assertSchemaReady
|
|
53
|
+
});
|
|
54
|
+
const handlers = createHotUpdaterHandlers(core.api, insights, clientAccess.type === "api-key" ? {
|
|
55
|
+
authenticate: (request) => authenticateApiKey({
|
|
56
|
+
apiKeys: plugin.models.apiKeys,
|
|
57
|
+
beforeLookup: assertSchemaReady,
|
|
58
|
+
headerName: clientAccess.headerName,
|
|
59
|
+
request
|
|
60
|
+
}),
|
|
61
|
+
headerName: clientAccess.headerName
|
|
62
|
+
} : void 0, downloadStorageObject);
|
|
63
|
+
const api = Object.assign({
|
|
43
64
|
adapterName: adapterCapabilities.adapterName ?? core.adapterName,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
65
|
+
insights,
|
|
66
|
+
apiKeys,
|
|
67
|
+
handlers
|
|
68
|
+
}, core.api);
|
|
47
69
|
Object.defineProperty(api, hotUpdaterCoreMetadata, {
|
|
48
70
|
enumerable: false,
|
|
49
71
|
value: {
|
|
@@ -3,10 +3,6 @@ let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
|
|
|
3
3
|
let _hot_updater_core = require("@hot-updater/core");
|
|
4
4
|
let node_crypto = require("node:crypto");
|
|
5
5
|
node_crypto = require_runtime.__toESM(node_crypto);
|
|
6
|
-
let node_fs_promises = require("node:fs/promises");
|
|
7
|
-
node_fs_promises = require_runtime.__toESM(node_fs_promises);
|
|
8
|
-
let node_os = require("node:os");
|
|
9
|
-
node_os = require_runtime.__toESM(node_os);
|
|
10
6
|
let node_path = require("node:path");
|
|
11
7
|
node_path = require_runtime.__toESM(node_path);
|
|
12
8
|
let node_util = require("node:util");
|
|
@@ -14,7 +10,6 @@ let node_zlib = require("node:zlib");
|
|
|
14
10
|
let _hot_updater_bsdiff = require("@hot-updater/bsdiff");
|
|
15
11
|
//#region src/db/createBundleDiff.ts
|
|
16
12
|
const HBC_ASSET_PATH_RE = /\.bundle$/;
|
|
17
|
-
const HOT_UPDATER_DOWNLOAD_DIR_PREFIX = "downloads-";
|
|
18
13
|
const decompressBrotli = (0, node_util.promisify)(node_zlib.brotliDecompress);
|
|
19
14
|
const isBundleManifest = (value) => {
|
|
20
15
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
@@ -39,23 +34,14 @@ async function downloadFromUrl(url) {
|
|
|
39
34
|
}
|
|
40
35
|
async function downloadStorageBytes(storageUri, storagePlugin) {
|
|
41
36
|
const protocol = new URL(storageUri).protocol.replace(":", "");
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
await node_fs_promises.default.mkdir(downloadRoot, { recursive: true });
|
|
47
|
-
const workDir = await node_fs_promises.default.mkdtemp(node_path.default.join(downloadRoot, HOT_UPDATER_DOWNLOAD_DIR_PREFIX));
|
|
48
|
-
const filename = node_path.default.basename(new URL(storageUri).pathname) || (0, node_crypto.randomUUID)();
|
|
49
|
-
const filePath = node_path.default.join(workDir, filename);
|
|
50
|
-
try {
|
|
51
|
-
await storagePlugin.profiles.node.downloadFile(storageUri, filePath);
|
|
52
|
-
return new Uint8Array(await node_fs_promises.default.readFile(filePath));
|
|
53
|
-
} finally {
|
|
54
|
-
await node_fs_promises.default.rm(workDir, {
|
|
55
|
-
force: true,
|
|
56
|
-
recursive: true
|
|
57
|
-
});
|
|
37
|
+
if (!storagePlugin || storagePlugin.protocol !== protocol) {
|
|
38
|
+
if (protocol === "http" || protocol === "https") return downloadFromUrl(storageUri);
|
|
39
|
+
if (!storagePlugin) throw new Error("Storage plugin is not configured");
|
|
40
|
+
throw new Error(`No storage plugin for protocol: ${protocol}`);
|
|
58
41
|
}
|
|
42
|
+
const { response } = await storagePlugin.get({ storageUri });
|
|
43
|
+
if (response === null) throw new Error(`Storage object not found: ${storageUri}`);
|
|
44
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
59
45
|
}
|
|
60
46
|
async function fetchManifest(bundle, storagePlugin) {
|
|
61
47
|
const manifestStorageUri = (0, _hot_updater_core.getManifestStorageUri)(bundle);
|
|
@@ -81,12 +67,16 @@ async function fetchAssetBytes(bundle, assetPath, manifest, storagePlugin) {
|
|
|
81
67
|
const compressedAssetStorageUri = (0, _hot_updater_plugin_core.resolveManifestAssetStorageUri)({
|
|
82
68
|
assetBaseStorageUri,
|
|
83
69
|
assetPath: downloadPath,
|
|
70
|
+
downloadFileHash: asset.downloadFileHash,
|
|
84
71
|
fileHash: asset.fileHash
|
|
85
72
|
});
|
|
86
73
|
let compressedBytes = null;
|
|
87
74
|
try {
|
|
88
75
|
compressedBytes = await downloadStorageBytes(compressedAssetStorageUri, storagePlugin);
|
|
89
|
-
} catch {
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (!(error instanceof Error)) throw error;
|
|
78
|
+
compressedBytes = null;
|
|
79
|
+
}
|
|
90
80
|
if (compressedBytes) return new Uint8Array(await decompressBrotli(compressedBytes));
|
|
91
81
|
}
|
|
92
82
|
return downloadStorageBytes((0, _hot_updater_plugin_core.resolveManifestAssetStorageUri)({
|
|
@@ -95,23 +85,16 @@ async function fetchAssetBytes(bundle, assetPath, manifest, storagePlugin) {
|
|
|
95
85
|
fileHash: asset.fileHash
|
|
96
86
|
}), storagePlugin);
|
|
97
87
|
}
|
|
98
|
-
async function getFileHash(filePath) {
|
|
99
|
-
const file = await node_fs_promises.default.readFile(filePath);
|
|
100
|
-
return node_crypto.default.createHash("sha256").update(file).digest("hex");
|
|
101
|
-
}
|
|
102
88
|
function buildNextPatchState({ currentBundle, nextPatch, makePrimary }) {
|
|
103
89
|
const existingPatches = (0, _hot_updater_core.getBundlePatches)(currentBundle).filter((patch) => patch.baseBundleId !== nextPatch.baseBundleId);
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
patches: orderedPatches,
|
|
107
|
-
primaryPatch: orderedPatches[0] ?? nextPatch
|
|
108
|
-
};
|
|
90
|
+
return makePrimary ? [nextPatch, ...existingPatches] : [...existingPatches, nextPatch];
|
|
109
91
|
}
|
|
110
92
|
async function createBundleDiff({ baseBundleId, bundleId }, deps, options = {}) {
|
|
93
|
+
const database = (0, _hot_updater_plugin_core.createDatabaseClient)(deps.databasePlugin);
|
|
111
94
|
if (!deps.storagePlugin) throw new Error("Storage plugin is not configured");
|
|
112
95
|
if (baseBundleId === bundleId) throw new Error("Base bundle must be different from the target bundle");
|
|
113
|
-
const baseBundle = await
|
|
114
|
-
const targetBundle = await
|
|
96
|
+
const baseBundle = await database.getBundleById(baseBundleId);
|
|
97
|
+
const targetBundle = await database.getBundleById(bundleId);
|
|
115
98
|
if (!baseBundle || !targetBundle) throw new Error("Bundle not found");
|
|
116
99
|
if (baseBundle.platform !== targetBundle.platform) throw new Error("Base bundle platform must match the target bundle");
|
|
117
100
|
if (baseBundle.id.localeCompare(targetBundle.id) >= 0) throw new Error("Base bundle must be older than the target bundle");
|
|
@@ -125,43 +108,37 @@ async function createBundleDiff({ baseBundleId, bundleId }, deps, options = {})
|
|
|
125
108
|
if (baseAssetHash === targetAssetHash) throw new Error("Hermes bundle is unchanged; no diff patch is required");
|
|
126
109
|
const [baseBytes, targetBytes] = await Promise.all([fetchAssetBytes(baseBundle, baseAssetPath, baseManifest, deps.storagePlugin), fetchAssetBytes(targetBundle, targetAssetPath, targetManifest, deps.storagePlugin)]);
|
|
127
110
|
const patchBytes = await (0, _hot_updater_bsdiff.hdiff)(baseBytes, targetBytes);
|
|
128
|
-
const
|
|
111
|
+
const patchFileHash = node_crypto.default.createHash("sha256").update(patchBytes).digest("hex");
|
|
129
112
|
const patchFilename = `${node_path.default.posix.basename(targetAssetPath)}.bsdiff`;
|
|
130
|
-
const patchPath = node_path.default.join(workDir, patchFilename);
|
|
131
113
|
const previousPatch = (0, _hot_updater_core.getBundlePatch)(targetBundle, baseBundle.id);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
await node_fs_promises.default.rm(workDir, {
|
|
161
|
-
force: true,
|
|
162
|
-
recursive: true
|
|
163
|
-
});
|
|
164
|
-
}
|
|
114
|
+
const uploadKey = (0, _hot_updater_plugin_core.createBundleStorageKey)(targetBundle.id, "patches", baseBundle.id, patchFileHash, getRelativeStorageDir(targetAssetPath), patchFilename);
|
|
115
|
+
const patchUpload = await deps.storagePlugin.put({
|
|
116
|
+
key: uploadKey,
|
|
117
|
+
body: new ReadableStream({ start(controller) {
|
|
118
|
+
controller.enqueue(patchBytes);
|
|
119
|
+
controller.close();
|
|
120
|
+
} }),
|
|
121
|
+
contentLength: patchBytes.byteLength,
|
|
122
|
+
contentType: "application/octet-stream"
|
|
123
|
+
});
|
|
124
|
+
const patches = buildNextPatchState({
|
|
125
|
+
currentBundle: targetBundle,
|
|
126
|
+
nextPatch: {
|
|
127
|
+
baseBundleId: baseBundle.id,
|
|
128
|
+
baseFileHash: baseAssetHash,
|
|
129
|
+
byteSize: patchBytes.byteLength,
|
|
130
|
+
patchFileHash,
|
|
131
|
+
patchStorageUri: patchUpload.storageUri
|
|
132
|
+
},
|
|
133
|
+
makePrimary: options.makePrimary ?? true
|
|
134
|
+
});
|
|
135
|
+
const updatedBundle = {
|
|
136
|
+
...targetBundle,
|
|
137
|
+
patches
|
|
138
|
+
};
|
|
139
|
+
await database.updateBundleById(targetBundle.id, updatedBundle);
|
|
140
|
+
if (previousPatch?.patchStorageUri && previousPatch.patchStorageUri !== patchUpload.storageUri) await deps.storagePlugin.delete({ storageUri: previousPatch.patchStorageUri }).catch(() => {});
|
|
141
|
+
return updatedBundle;
|
|
165
142
|
}
|
|
166
143
|
//#endregion
|
|
167
144
|
exports.createBundleDiff = createBundleDiff;
|