@hot-updater/plugin-core 0.21.6 → 0.21.8

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.
Files changed (46) hide show
  1. package/dist/_virtual/rolldown_runtime.cjs +25 -0
  2. package/dist/calculatePagination.cjs +27 -0
  3. package/dist/calculatePagination.d.cts +17 -0
  4. package/dist/calculatePagination.d.ts +17 -0
  5. package/dist/calculatePagination.js +26 -0
  6. package/dist/compressionFormat.cjs +59 -0
  7. package/dist/compressionFormat.d.cts +33 -0
  8. package/dist/compressionFormat.d.ts +33 -0
  9. package/dist/compressionFormat.js +54 -0
  10. package/dist/createBlobDatabasePlugin.cjs +312 -0
  11. package/dist/createBlobDatabasePlugin.d.cts +38 -0
  12. package/dist/createBlobDatabasePlugin.d.ts +38 -0
  13. package/dist/createBlobDatabasePlugin.js +309 -0
  14. package/dist/createDatabasePlugin.cjs +97 -0
  15. package/dist/createDatabasePlugin.d.cts +68 -0
  16. package/dist/createDatabasePlugin.d.ts +68 -0
  17. package/dist/createDatabasePlugin.js +95 -0
  18. package/dist/createStorageKeyBuilder.cjs +8 -0
  19. package/dist/createStorageKeyBuilder.d.cts +4 -0
  20. package/dist/createStorageKeyBuilder.d.ts +4 -0
  21. package/dist/createStorageKeyBuilder.js +7 -0
  22. package/dist/filterCompatibleAppVersions.cjs +17 -0
  23. package/dist/filterCompatibleAppVersions.d.cts +12 -0
  24. package/dist/filterCompatibleAppVersions.d.ts +12 -0
  25. package/dist/filterCompatibleAppVersions.js +17 -0
  26. package/dist/generateMinBundleId.cjs +11 -0
  27. package/dist/generateMinBundleId.d.cts +4 -0
  28. package/dist/generateMinBundleId.d.ts +4 -0
  29. package/dist/generateMinBundleId.js +10 -0
  30. package/dist/index.cjs +21 -27391
  31. package/dist/index.d.cts +12 -560
  32. package/dist/index.d.ts +12 -560
  33. package/dist/index.js +11 -27351
  34. package/dist/parseStorageUri.cjs +35 -0
  35. package/dist/parseStorageUri.d.cts +24 -0
  36. package/dist/parseStorageUri.d.ts +24 -0
  37. package/dist/parseStorageUri.js +34 -0
  38. package/dist/semverSatisfies.cjs +13 -0
  39. package/dist/semverSatisfies.d.cts +4 -0
  40. package/dist/semverSatisfies.d.ts +4 -0
  41. package/dist/semverSatisfies.js +11 -0
  42. package/dist/types/index.d.cts +201 -0
  43. package/dist/types/index.d.ts +201 -0
  44. package/dist/types/utils.d.cts +12 -0
  45. package/dist/types/utils.d.ts +12 -0
  46. package/package.json +5 -14
@@ -0,0 +1,25 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+
25
+ exports.__toESM = __toESM;
@@ -0,0 +1,27 @@
1
+
2
+ //#region src/calculatePagination.ts
3
+ /**
4
+ * Calculate pagination information based on total count, limit, and offset
5
+ */
6
+ function calculatePagination(total, options) {
7
+ const { limit, offset } = options;
8
+ if (total === 0) return {
9
+ total: 0,
10
+ hasNextPage: false,
11
+ hasPreviousPage: false,
12
+ currentPage: 1,
13
+ totalPages: 0
14
+ };
15
+ const currentPage = Math.floor(offset / limit) + 1;
16
+ const totalPages = Math.ceil(total / limit);
17
+ return {
18
+ total,
19
+ hasNextPage: offset + limit < total,
20
+ hasPreviousPage: offset > 0,
21
+ currentPage,
22
+ totalPages
23
+ };
24
+ }
25
+
26
+ //#endregion
27
+ exports.calculatePagination = calculatePagination;
@@ -0,0 +1,17 @@
1
+ import { Bundle, PaginationInfo } from "./types/index.cjs";
2
+
3
+ //#region src/calculatePagination.d.ts
4
+ interface PaginationOptions {
5
+ limit: number;
6
+ offset: number;
7
+ }
8
+ interface PaginatedResult {
9
+ data: Bundle[];
10
+ pagination: PaginationInfo;
11
+ }
12
+ /**
13
+ * Calculate pagination information based on total count, limit, and offset
14
+ */
15
+ declare function calculatePagination(total: number, options: PaginationOptions): PaginationInfo;
16
+ //#endregion
17
+ export { PaginatedResult, PaginationOptions, calculatePagination };
@@ -0,0 +1,17 @@
1
+ import { Bundle, PaginationInfo } from "./types/index.js";
2
+
3
+ //#region src/calculatePagination.d.ts
4
+ interface PaginationOptions {
5
+ limit: number;
6
+ offset: number;
7
+ }
8
+ interface PaginatedResult {
9
+ data: Bundle[];
10
+ pagination: PaginationInfo;
11
+ }
12
+ /**
13
+ * Calculate pagination information based on total count, limit, and offset
14
+ */
15
+ declare function calculatePagination(total: number, options: PaginationOptions): PaginationInfo;
16
+ //#endregion
17
+ export { PaginatedResult, PaginationOptions, calculatePagination };
@@ -0,0 +1,26 @@
1
+ //#region src/calculatePagination.ts
2
+ /**
3
+ * Calculate pagination information based on total count, limit, and offset
4
+ */
5
+ function calculatePagination(total, options) {
6
+ const { limit, offset } = options;
7
+ if (total === 0) return {
8
+ total: 0,
9
+ hasNextPage: false,
10
+ hasPreviousPage: false,
11
+ currentPage: 1,
12
+ totalPages: 0
13
+ };
14
+ const currentPage = Math.floor(offset / limit) + 1;
15
+ const totalPages = Math.ceil(total / limit);
16
+ return {
17
+ total,
18
+ hasNextPage: offset + limit < total,
19
+ hasPreviousPage: offset > 0,
20
+ currentPage,
21
+ totalPages
22
+ };
23
+ }
24
+
25
+ //#endregion
26
+ export { calculatePagination };
@@ -0,0 +1,59 @@
1
+ const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
2
+ let mime = require("mime");
3
+ mime = require_rolldown_runtime.__toESM(mime);
4
+ let path = require("path");
5
+ path = require_rolldown_runtime.__toESM(path);
6
+
7
+ //#region src/compressionFormat.ts
8
+ /**
9
+ * Compression formats registry
10
+ * Add new formats here to support additional compression types
11
+ */
12
+ const COMPRESSION_FORMATS = {
13
+ zip: {
14
+ format: "zip",
15
+ fileExtension: ".zip",
16
+ mimeType: "application/zip"
17
+ },
18
+ "tar.br": {
19
+ format: "tar.br",
20
+ fileExtension: ".tar.br",
21
+ mimeType: "application/x-tar"
22
+ },
23
+ "tar.gz": {
24
+ format: "tar.gz",
25
+ fileExtension: ".tar.gz",
26
+ mimeType: "application/x-tar"
27
+ }
28
+ };
29
+ /**
30
+ * Detects compression format from filename
31
+ * @param filename The filename to detect format from
32
+ * @returns Compression format information
33
+ */
34
+ function detectCompressionFormat(filename) {
35
+ for (const info of Object.values(COMPRESSION_FORMATS)) if (filename.endsWith(info.fileExtension)) return info;
36
+ return COMPRESSION_FORMATS.zip;
37
+ }
38
+ /**
39
+ * Gets MIME type for a filename
40
+ * @param filename The filename to get MIME type for
41
+ * @returns MIME type string
42
+ */
43
+ function getCompressionMimeType(filename) {
44
+ return detectCompressionFormat(filename).mimeType;
45
+ }
46
+ /**
47
+ * Gets Content-Type for a bundle file with 3-tier fallback
48
+ * @param bundlePath The bundle file path
49
+ * @returns Content-Type string (never undefined, falls back to application/octet-stream)
50
+ */
51
+ function getContentType(bundlePath) {
52
+ const filename = path.default.basename(bundlePath);
53
+ return mime.default.getType(bundlePath) ?? getCompressionMimeType(filename) ?? "application/octet-stream";
54
+ }
55
+
56
+ //#endregion
57
+ exports.detectCompressionFormat = detectCompressionFormat;
58
+ exports.getCompressionMimeType = getCompressionMimeType;
59
+ exports.getContentType = getContentType;
@@ -0,0 +1,33 @@
1
+ //#region src/compressionFormat.d.ts
2
+ /**
3
+ * Compression format type definition
4
+ */
5
+ type CompressionFormat = "zip" | "tar.br" | "tar.gz";
6
+ /**
7
+ * Compression format metadata
8
+ */
9
+ interface CompressionFormatInfo {
10
+ format: CompressionFormat;
11
+ fileExtension: string;
12
+ mimeType?: string;
13
+ }
14
+ /**
15
+ * Detects compression format from filename
16
+ * @param filename The filename to detect format from
17
+ * @returns Compression format information
18
+ */
19
+ declare function detectCompressionFormat(filename: string): CompressionFormatInfo;
20
+ /**
21
+ * Gets MIME type for a filename
22
+ * @param filename The filename to get MIME type for
23
+ * @returns MIME type string
24
+ */
25
+ declare function getCompressionMimeType(filename: string): string | undefined;
26
+ /**
27
+ * Gets Content-Type for a bundle file with 3-tier fallback
28
+ * @param bundlePath The bundle file path
29
+ * @returns Content-Type string (never undefined, falls back to application/octet-stream)
30
+ */
31
+ declare function getContentType(bundlePath: string): string;
32
+ //#endregion
33
+ export { CompressionFormat, CompressionFormatInfo, detectCompressionFormat, getCompressionMimeType, getContentType };
@@ -0,0 +1,33 @@
1
+ //#region src/compressionFormat.d.ts
2
+ /**
3
+ * Compression format type definition
4
+ */
5
+ type CompressionFormat = "zip" | "tar.br" | "tar.gz";
6
+ /**
7
+ * Compression format metadata
8
+ */
9
+ interface CompressionFormatInfo {
10
+ format: CompressionFormat;
11
+ fileExtension: string;
12
+ mimeType?: string;
13
+ }
14
+ /**
15
+ * Detects compression format from filename
16
+ * @param filename The filename to detect format from
17
+ * @returns Compression format information
18
+ */
19
+ declare function detectCompressionFormat(filename: string): CompressionFormatInfo;
20
+ /**
21
+ * Gets MIME type for a filename
22
+ * @param filename The filename to get MIME type for
23
+ * @returns MIME type string
24
+ */
25
+ declare function getCompressionMimeType(filename: string): string | undefined;
26
+ /**
27
+ * Gets Content-Type for a bundle file with 3-tier fallback
28
+ * @param bundlePath The bundle file path
29
+ * @returns Content-Type string (never undefined, falls back to application/octet-stream)
30
+ */
31
+ declare function getContentType(bundlePath: string): string;
32
+ //#endregion
33
+ export { CompressionFormat, CompressionFormatInfo, detectCompressionFormat, getCompressionMimeType, getContentType };
@@ -0,0 +1,54 @@
1
+ import mime from "mime";
2
+ import path from "path";
3
+
4
+ //#region src/compressionFormat.ts
5
+ /**
6
+ * Compression formats registry
7
+ * Add new formats here to support additional compression types
8
+ */
9
+ const COMPRESSION_FORMATS = {
10
+ zip: {
11
+ format: "zip",
12
+ fileExtension: ".zip",
13
+ mimeType: "application/zip"
14
+ },
15
+ "tar.br": {
16
+ format: "tar.br",
17
+ fileExtension: ".tar.br",
18
+ mimeType: "application/x-tar"
19
+ },
20
+ "tar.gz": {
21
+ format: "tar.gz",
22
+ fileExtension: ".tar.gz",
23
+ mimeType: "application/x-tar"
24
+ }
25
+ };
26
+ /**
27
+ * Detects compression format from filename
28
+ * @param filename The filename to detect format from
29
+ * @returns Compression format information
30
+ */
31
+ function detectCompressionFormat(filename) {
32
+ for (const info of Object.values(COMPRESSION_FORMATS)) if (filename.endsWith(info.fileExtension)) return info;
33
+ return COMPRESSION_FORMATS.zip;
34
+ }
35
+ /**
36
+ * Gets MIME type for a filename
37
+ * @param filename The filename to get MIME type for
38
+ * @returns MIME type string
39
+ */
40
+ function getCompressionMimeType(filename) {
41
+ return detectCompressionFormat(filename).mimeType;
42
+ }
43
+ /**
44
+ * Gets Content-Type for a bundle file with 3-tier fallback
45
+ * @param bundlePath The bundle file path
46
+ * @returns Content-Type string (never undefined, falls back to application/octet-stream)
47
+ */
48
+ function getContentType(bundlePath) {
49
+ const filename = path.basename(bundlePath);
50
+ return mime.getType(bundlePath) ?? getCompressionMimeType(filename) ?? "application/octet-stream";
51
+ }
52
+
53
+ //#endregion
54
+ export { detectCompressionFormat, getCompressionMimeType, getContentType };
@@ -0,0 +1,312 @@
1
+ const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
2
+ const require_calculatePagination = require('./calculatePagination.cjs');
3
+ const require_createDatabasePlugin = require('./createDatabasePlugin.cjs');
4
+ let es_toolkit = require("es-toolkit");
5
+ es_toolkit = require_rolldown_runtime.__toESM(es_toolkit);
6
+ let semver = require("semver");
7
+ semver = require_rolldown_runtime.__toESM(semver);
8
+
9
+ //#region src/createBlobDatabasePlugin.ts
10
+ function removeBundleInternalKeys(bundle) {
11
+ const { _updateJsonKey, _oldUpdateJsonKey,...pureBundle } = bundle;
12
+ return pureBundle;
13
+ }
14
+ function normalizeTargetAppVersion(version) {
15
+ if (!version) return null;
16
+ let normalized = version.replace(/\s+/g, " ").trim();
17
+ normalized = normalized.replace(/([><=~^]+)\s+(\d)/g, (_match, operator, digit) => `${operator}${digit}`);
18
+ return normalized;
19
+ }
20
+ function isExactVersion(version) {
21
+ if (!version) return false;
22
+ const normalized = normalizeTargetAppVersion(version);
23
+ if (!normalized) return false;
24
+ return semver.default.valid(normalized) !== null;
25
+ }
26
+ /**
27
+ * Get all normalized semver versions for a version string.
28
+ * This handles the case where clients may request with different normalized forms.
29
+ *
30
+ * Examples:
31
+ * - "1.0.0" generates ["1.0.0", "1.0", "1"]
32
+ * - "2.1.0" generates ["2.1.0", "2.1"]
33
+ * - "1.2.3" generates ["1.2.3"]
34
+ */
35
+ function getSemverNormalizedVersions(version) {
36
+ const normalized = normalizeTargetAppVersion(version) || version;
37
+ const coerced = semver.default.coerce(normalized);
38
+ if (!coerced) return [normalized];
39
+ const versions = /* @__PURE__ */ new Set();
40
+ versions.add(coerced.version);
41
+ if (coerced.patch === 0) versions.add(`${coerced.major}.${coerced.minor}`);
42
+ if (coerced.minor === 0 && coerced.patch === 0) versions.add(`${coerced.major}`);
43
+ return Array.from(versions);
44
+ }
45
+ /**
46
+ *
47
+ * @param name - The name of the database plugin
48
+ * @param listObjects - Function to list objects in the storage
49
+ * @param loadObject - Function to load an JSON object from the storage
50
+ * @param uploadObject - Function to upload an JSON object to the storage
51
+ * @param deleteObject - Function to delete an object from the storage
52
+ * @param invalidatePaths - Function to invalidate paths in the CDN
53
+ * @param hooks - Optional hooks for additional functionality - see createDatabasePlugin
54
+ * @returns
55
+ */
56
+ const createBlobDatabasePlugin = ({ name, getContext, listObjects, loadObject, uploadObject, deleteObject, invalidatePaths, hooks, apiBasePath }) => {
57
+ const bundlesMap = /* @__PURE__ */ new Map();
58
+ const pendingBundlesMap = /* @__PURE__ */ new Map();
59
+ const PLATFORMS = ["ios", "android"];
60
+ async function reloadBundles(context) {
61
+ bundlesMap.clear();
62
+ const platformPromises = PLATFORMS.map(async (platform) => {
63
+ const filePromises = (await listUpdateJsonKeys(context, platform)).map(async (key) => {
64
+ return (await loadObject(context, key) ?? []).map((bundle) => ({
65
+ ...bundle,
66
+ _updateJsonKey: key
67
+ }));
68
+ });
69
+ return (await Promise.all(filePromises)).flat();
70
+ });
71
+ const allBundles = (await Promise.all(platformPromises)).flat();
72
+ for (const bundle of allBundles) bundlesMap.set(bundle.id, bundle);
73
+ for (const [id, bundle] of pendingBundlesMap.entries()) bundlesMap.set(id, bundle);
74
+ return (0, es_toolkit.orderBy)(allBundles, [(v) => v.id], ["desc"]);
75
+ }
76
+ /**
77
+ * Updates target-app-versions.json for each channel on the given platform.
78
+ * Returns true if the file was updated, false if no changes were made.
79
+ */
80
+ async function updateTargetVersionsForPlatform(context, platform) {
81
+ const pattern = /* @__PURE__ */ new RegExp(`^[^/]+/${platform}/[^/]+/update\\.json$`);
82
+ const keysByChannel = (await listObjects(context, "")).filter((key) => pattern.test(key)).reduce((acc, key) => {
83
+ const channel = key.split("/")[0];
84
+ acc[channel] = acc[channel] || [];
85
+ acc[channel].push(key);
86
+ return acc;
87
+ }, {});
88
+ const updatedTargetFiles = /* @__PURE__ */ new Set();
89
+ for (const channel of Object.keys(keysByChannel)) {
90
+ const updateKeys = keysByChannel[channel];
91
+ const targetKey = `${channel}/${platform}/target-app-versions.json`;
92
+ const currentVersions = updateKeys.map((key) => key.split("/")[2]);
93
+ const oldTargetVersions = await loadObject(context, targetKey) ?? [];
94
+ const newTargetVersions = oldTargetVersions.filter((v) => currentVersions.includes(v));
95
+ for (const v of currentVersions) if (!newTargetVersions.includes(v)) newTargetVersions.push(v);
96
+ if (JSON.stringify(oldTargetVersions) !== JSON.stringify(newTargetVersions)) {
97
+ await uploadObject(context, targetKey, newTargetVersions);
98
+ updatedTargetFiles.add(`/${targetKey}`);
99
+ }
100
+ }
101
+ return updatedTargetFiles;
102
+ }
103
+ /**
104
+ * Lists update.json keys for a given platform.
105
+ *
106
+ * - If a channel is provided, only that channel's update.json files are listed.
107
+ * - Otherwise, all channels for the given platform are returned.
108
+ */
109
+ async function listUpdateJsonKeys(context, platform, channel) {
110
+ const prefix = channel ? platform ? `${channel}/${platform}/` : `${channel}/` : "";
111
+ const pattern = channel ? platform ? /* @__PURE__ */ new RegExp(`^${channel}/${platform}/[^/]+/update\\.json$`) : /* @__PURE__ */ new RegExp(`^${channel}/[^/]+/[^/]+/update\\.json$`) : platform ? /* @__PURE__ */ new RegExp(`^[^/]+/${platform}/[^/]+/update\\.json$`) : /^[^/]+\/[^/]+\/[^/]+\/update\.json$/;
112
+ return listObjects(context, prefix).then((keys) => keys.filter((key) => pattern.test(key)));
113
+ }
114
+ return require_createDatabasePlugin.createDatabasePlugin(name, {
115
+ getContext,
116
+ async getBundleById(context, bundleId) {
117
+ const pendingBundle = pendingBundlesMap.get(bundleId);
118
+ if (pendingBundle) return removeBundleInternalKeys(pendingBundle);
119
+ const bundle = bundlesMap.get(bundleId);
120
+ if (bundle) return removeBundleInternalKeys(bundle);
121
+ return (await reloadBundles(context)).find((bundle$1) => bundle$1.id === bundleId) ?? null;
122
+ },
123
+ async getBundles(context, options) {
124
+ let allBundles = await reloadBundles(context);
125
+ const { where, limit, offset } = options;
126
+ if (where) allBundles = allBundles.filter((bundle) => {
127
+ return Object.entries(where).every(([key, value]) => value === void 0 || value === null || bundle[key] === value);
128
+ });
129
+ const total = allBundles.length;
130
+ let paginatedData = allBundles.map(removeBundleInternalKeys);
131
+ if (offset > 0) paginatedData = paginatedData.slice(offset);
132
+ if (limit) paginatedData = paginatedData.slice(0, limit);
133
+ return {
134
+ data: paginatedData,
135
+ pagination: require_calculatePagination.calculatePagination(total, {
136
+ limit,
137
+ offset
138
+ })
139
+ };
140
+ },
141
+ async getChannels(context) {
142
+ const total = (await reloadBundles(context)).length;
143
+ const result = await this.getBundles(context, {
144
+ limit: total,
145
+ offset: 0
146
+ });
147
+ return [...new Set(result.data.map((bundle) => bundle.channel))];
148
+ },
149
+ async commitBundle(context, { changedSets }) {
150
+ if (changedSets.length === 0) return;
151
+ const changedBundlesByKey = {};
152
+ const removalsByKey = {};
153
+ const pathsToInvalidate = /* @__PURE__ */ new Set();
154
+ let isTargetAppVersionChanged = false;
155
+ for (const { operation, data } of changedSets) {
156
+ if (data.targetAppVersion !== void 0) isTargetAppVersionChanged = true;
157
+ if (operation === "insert") {
158
+ const target = normalizeTargetAppVersion(data.targetAppVersion) ?? data.fingerprintHash;
159
+ if (!target) throw new Error("target not found");
160
+ const key = `${data.channel}/${data.platform}/${target}/update.json`;
161
+ const bundleWithKey = {
162
+ ...data,
163
+ _updateJsonKey: key
164
+ };
165
+ bundlesMap.set(data.id, bundleWithKey);
166
+ pendingBundlesMap.set(data.id, bundleWithKey);
167
+ changedBundlesByKey[key] = changedBundlesByKey[key] || [];
168
+ changedBundlesByKey[key].push(removeBundleInternalKeys(bundleWithKey));
169
+ pathsToInvalidate.add(`/${key}`);
170
+ if (data.fingerprintHash) pathsToInvalidate.add(`${apiBasePath}/fingerprint/${data.platform}/${data.fingerprintHash}/${data.channel}/*`);
171
+ else if (data.targetAppVersion) if (!isExactVersion(data.targetAppVersion)) pathsToInvalidate.add(`${apiBasePath}/app-version/${data.platform}/*`);
172
+ else {
173
+ const normalizedVersions = getSemverNormalizedVersions(data.targetAppVersion);
174
+ for (const version of normalizedVersions) pathsToInvalidate.add(`${apiBasePath}/app-version/${data.platform}/${version}/${data.channel}/*`);
175
+ }
176
+ continue;
177
+ }
178
+ if (operation === "delete") {
179
+ let bundle$1 = pendingBundlesMap.get(data.id);
180
+ if (!bundle$1) bundle$1 = bundlesMap.get(data.id);
181
+ if (!bundle$1) throw new Error("Bundle to delete not found");
182
+ bundlesMap.delete(data.id);
183
+ pendingBundlesMap.delete(data.id);
184
+ const key = bundle$1._updateJsonKey;
185
+ removalsByKey[key] = removalsByKey[key] || [];
186
+ removalsByKey[key].push(bundle$1.id);
187
+ pathsToInvalidate.add(`/${key}`);
188
+ if (bundle$1.fingerprintHash) pathsToInvalidate.add(`${apiBasePath}/fingerprint/${bundle$1.platform}/${bundle$1.fingerprintHash}/${bundle$1.channel}/*`);
189
+ else if (bundle$1.targetAppVersion) if (!isExactVersion(bundle$1.targetAppVersion)) pathsToInvalidate.add(`${apiBasePath}/app-version/${bundle$1.platform}/*`);
190
+ else {
191
+ const normalizedVersions = getSemverNormalizedVersions(bundle$1.targetAppVersion);
192
+ for (const version of normalizedVersions) pathsToInvalidate.add(`${apiBasePath}/app-version/${bundle$1.platform}/${version}/${bundle$1.channel}/*`);
193
+ }
194
+ continue;
195
+ }
196
+ let bundle = pendingBundlesMap.get(data.id);
197
+ if (!bundle) bundle = bundlesMap.get(data.id);
198
+ if (!bundle) throw new Error("targetBundleId not found");
199
+ if (operation === "update") {
200
+ const newChannel = data.channel !== void 0 ? data.channel : bundle.channel;
201
+ const newPlatform = data.platform !== void 0 ? data.platform : bundle.platform;
202
+ const target = data.fingerprintHash ?? bundle.fingerprintHash ?? normalizeTargetAppVersion(data.targetAppVersion) ?? normalizeTargetAppVersion(bundle.targetAppVersion);
203
+ if (!target) throw new Error("target not found");
204
+ const newKey = `${newChannel}/${newPlatform}/${target}/update.json`;
205
+ if (newKey !== bundle._updateJsonKey) {
206
+ const oldKey = bundle._updateJsonKey;
207
+ removalsByKey[oldKey] = removalsByKey[oldKey] || [];
208
+ removalsByKey[oldKey].push(bundle.id);
209
+ changedBundlesByKey[newKey] = changedBundlesByKey[newKey] || [];
210
+ const updatedBundle$1 = {
211
+ ...bundle,
212
+ ...data
213
+ };
214
+ updatedBundle$1._oldUpdateJsonKey = oldKey;
215
+ updatedBundle$1._updateJsonKey = newKey;
216
+ bundlesMap.set(data.id, updatedBundle$1);
217
+ pendingBundlesMap.set(data.id, updatedBundle$1);
218
+ changedBundlesByKey[newKey].push(removeBundleInternalKeys(updatedBundle$1));
219
+ pathsToInvalidate.add(`/${oldKey}`);
220
+ pathsToInvalidate.add(`/${newKey}`);
221
+ const oldChannel = bundle.channel;
222
+ const newChannel$1 = data.channel;
223
+ if (oldChannel !== newChannel$1) {
224
+ pathsToInvalidate.add(`/${oldChannel}/${bundle.platform}/target-app-versions.json`);
225
+ pathsToInvalidate.add(`/${newChannel$1}/${bundle.platform}/target-app-versions.json`);
226
+ if (bundle.fingerprintHash) {
227
+ pathsToInvalidate.add(`${apiBasePath}/fingerprint/${bundle.platform}/${bundle.fingerprintHash}/${oldChannel}/*`);
228
+ pathsToInvalidate.add(`${apiBasePath}/fingerprint/${bundle.platform}/${bundle.fingerprintHash}/${newChannel$1}/*`);
229
+ }
230
+ if (bundle.targetAppVersion) if (!isExactVersion(bundle.targetAppVersion)) pathsToInvalidate.add(`${apiBasePath}/app-version/${bundle.platform}/*`);
231
+ else {
232
+ const normalizedVersions = getSemverNormalizedVersions(bundle.targetAppVersion);
233
+ for (const version of normalizedVersions) {
234
+ pathsToInvalidate.add(`${apiBasePath}/app-version/${bundle.platform}/${version}/${oldChannel}/*`);
235
+ pathsToInvalidate.add(`${apiBasePath}/app-version/${bundle.platform}/${version}/${newChannel$1}/*`);
236
+ }
237
+ }
238
+ }
239
+ if (updatedBundle$1.fingerprintHash) pathsToInvalidate.add(`${apiBasePath}/fingerprint/${bundle.platform}/${updatedBundle$1.fingerprintHash}/${updatedBundle$1.channel}/*`);
240
+ else if (updatedBundle$1.targetAppVersion) {
241
+ if (!isExactVersion(updatedBundle$1.targetAppVersion)) pathsToInvalidate.add(`${apiBasePath}/app-version/${updatedBundle$1.platform}/*`);
242
+ else {
243
+ const normalizedVersions = getSemverNormalizedVersions(updatedBundle$1.targetAppVersion);
244
+ for (const version of normalizedVersions) pathsToInvalidate.add(`${apiBasePath}/app-version/${updatedBundle$1.platform}/${version}/${updatedBundle$1.channel}/*`);
245
+ }
246
+ if (bundle.targetAppVersion && bundle.targetAppVersion !== updatedBundle$1.targetAppVersion) if (!isExactVersion(bundle.targetAppVersion)) pathsToInvalidate.add(`${apiBasePath}/app-version/${bundle.platform}/*`);
247
+ else {
248
+ const oldNormalizedVersions = getSemverNormalizedVersions(bundle.targetAppVersion);
249
+ for (const version of oldNormalizedVersions) pathsToInvalidate.add(`${apiBasePath}/app-version/${bundle.platform}/${version}/${bundle.channel}/*`);
250
+ }
251
+ }
252
+ continue;
253
+ }
254
+ const currentKey = bundle._updateJsonKey;
255
+ const updatedBundle = {
256
+ ...bundle,
257
+ ...data
258
+ };
259
+ bundlesMap.set(data.id, updatedBundle);
260
+ pendingBundlesMap.set(data.id, updatedBundle);
261
+ changedBundlesByKey[currentKey] = changedBundlesByKey[currentKey] || [];
262
+ changedBundlesByKey[currentKey].push(removeBundleInternalKeys(updatedBundle));
263
+ pathsToInvalidate.add(`/${currentKey}`);
264
+ if (updatedBundle.fingerprintHash) pathsToInvalidate.add(`${apiBasePath}/fingerprint/${updatedBundle.platform}/${updatedBundle.fingerprintHash}/${updatedBundle.channel}/*`);
265
+ else if (updatedBundle.targetAppVersion) {
266
+ if (!isExactVersion(updatedBundle.targetAppVersion)) pathsToInvalidate.add(`${apiBasePath}/app-version/${updatedBundle.platform}/*`);
267
+ else {
268
+ const normalizedVersions = getSemverNormalizedVersions(updatedBundle.targetAppVersion);
269
+ for (const version of normalizedVersions) pathsToInvalidate.add(`${apiBasePath}/app-version/${updatedBundle.platform}/${version}/${updatedBundle.channel}/*`);
270
+ }
271
+ if (bundle.targetAppVersion && bundle.targetAppVersion !== updatedBundle.targetAppVersion) if (!isExactVersion(bundle.targetAppVersion)) pathsToInvalidate.add(`${apiBasePath}/app-version/${bundle.platform}/*`);
272
+ else {
273
+ const oldNormalizedVersions = getSemverNormalizedVersions(bundle.targetAppVersion);
274
+ for (const version of oldNormalizedVersions) pathsToInvalidate.add(`${apiBasePath}/app-version/${bundle.platform}/${version}/${bundle.channel}/*`);
275
+ }
276
+ }
277
+ }
278
+ }
279
+ for (const oldKey of Object.keys(removalsByKey)) await (async () => {
280
+ const updatedBundles = (await loadObject(context, oldKey) ?? []).filter((b) => !removalsByKey[oldKey].includes(b.id));
281
+ updatedBundles.sort((a, b) => b.id.localeCompare(a.id));
282
+ if (updatedBundles.length === 0) await deleteObject(context, oldKey);
283
+ else await uploadObject(context, oldKey, updatedBundles);
284
+ })();
285
+ for (const key of Object.keys(changedBundlesByKey)) await (async () => {
286
+ const currentBundles = await loadObject(context, key) ?? [];
287
+ const pureBundles = changedBundlesByKey[key].map((bundle) => bundle);
288
+ for (const changedBundle of pureBundles) {
289
+ const index = currentBundles.findIndex((b) => b.id === changedBundle.id);
290
+ if (index >= 0) currentBundles[index] = changedBundle;
291
+ else currentBundles.push(changedBundle);
292
+ }
293
+ currentBundles.sort((a, b) => b.id.localeCompare(a.id));
294
+ await uploadObject(context, key, currentBundles);
295
+ })();
296
+ const updatedTargetFilePaths = /* @__PURE__ */ new Set();
297
+ if (isTargetAppVersionChanged) for (const platform of PLATFORMS) {
298
+ const updatedPaths = await updateTargetVersionsForPlatform(context, platform);
299
+ for (const path of updatedPaths) updatedTargetFilePaths.add(path);
300
+ }
301
+ for (const path of updatedTargetFilePaths) pathsToInvalidate.add(path);
302
+ const encondedPaths = /* @__PURE__ */ new Set();
303
+ for (const path of pathsToInvalidate) encondedPaths.add(encodeURI(path));
304
+ await invalidatePaths(context, Array.from(encondedPaths));
305
+ pendingBundlesMap.clear();
306
+ hooks?.onDatabaseUpdated?.();
307
+ }
308
+ }, hooks);
309
+ };
310
+
311
+ //#endregion
312
+ exports.createBlobDatabasePlugin = createBlobDatabasePlugin;
@@ -0,0 +1,38 @@
1
+ import { BasePluginArgs, DatabasePlugin, DatabasePluginHooks } from "./types/index.cjs";
2
+
3
+ //#region src/createBlobDatabasePlugin.d.ts
4
+
5
+ /**
6
+ *
7
+ * @param name - The name of the database plugin
8
+ * @param listObjects - Function to list objects in the storage
9
+ * @param loadObject - Function to load an JSON object from the storage
10
+ * @param uploadObject - Function to upload an JSON object to the storage
11
+ * @param deleteObject - Function to delete an object from the storage
12
+ * @param invalidatePaths - Function to invalidate paths in the CDN
13
+ * @param hooks - Optional hooks for additional functionality - see createDatabasePlugin
14
+ * @returns
15
+ */
16
+ declare const createBlobDatabasePlugin: <TContext = object>({
17
+ name,
18
+ getContext,
19
+ listObjects,
20
+ loadObject,
21
+ uploadObject,
22
+ deleteObject,
23
+ invalidatePaths,
24
+ hooks,
25
+ apiBasePath
26
+ }: {
27
+ name: string;
28
+ getContext: () => TContext;
29
+ listObjects: (context: TContext, prefix: string) => Promise<string[]>;
30
+ loadObject: <T>(context: TContext, key: string) => Promise<T | null>;
31
+ uploadObject: <T>(context: TContext, key: string, data: T) => Promise<void>;
32
+ deleteObject: (context: TContext, key: string) => Promise<void>;
33
+ invalidatePaths: (context: TContext, paths: string[]) => Promise<void>;
34
+ hooks?: DatabasePluginHooks;
35
+ apiBasePath: string;
36
+ }) => (options: BasePluginArgs) => DatabasePlugin;
37
+ //#endregion
38
+ export { createBlobDatabasePlugin };