@hot-updater/server 0.27.1 → 0.29.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.
Files changed (84) hide show
  1. package/dist/adapters/drizzle.cjs +7 -7
  2. package/dist/adapters/drizzle.mjs +2 -0
  3. package/dist/adapters/kysely.cjs +7 -7
  4. package/dist/adapters/kysely.mjs +2 -0
  5. package/dist/adapters/mongodb.cjs +7 -7
  6. package/dist/adapters/mongodb.mjs +2 -0
  7. package/dist/adapters/prisma.cjs +7 -7
  8. package/dist/adapters/prisma.mjs +2 -0
  9. package/dist/calculatePagination.cjs +1 -3
  10. package/dist/{calculatePagination.js → calculatePagination.mjs} +1 -2
  11. package/dist/db/index.cjs +24 -15
  12. package/dist/db/index.d.cts +12 -9
  13. package/dist/db/index.d.mts +30 -0
  14. package/dist/db/index.mjs +45 -0
  15. package/dist/db/ormCore.cjs +247 -138
  16. package/dist/db/ormCore.d.cts +35 -17
  17. package/dist/db/ormCore.d.mts +44 -0
  18. package/dist/db/ormCore.mjs +386 -0
  19. package/dist/db/pluginCore.cjs +145 -40
  20. package/dist/db/pluginCore.mjs +176 -0
  21. package/dist/db/types.cjs +1 -3
  22. package/dist/db/types.d.cts +14 -21
  23. package/dist/db/types.d.mts +24 -0
  24. package/dist/db/{types.js → types.mjs} +1 -2
  25. package/dist/handler.cjs +117 -48
  26. package/dist/handler.d.cts +28 -18
  27. package/dist/handler.d.mts +47 -0
  28. package/dist/handler.mjs +217 -0
  29. package/dist/index.cjs +5 -5
  30. package/dist/index.d.cts +3 -3
  31. package/dist/index.d.mts +5 -0
  32. package/dist/index.mjs +4 -0
  33. package/dist/internalRouter.cjs +54 -0
  34. package/dist/internalRouter.mjs +52 -0
  35. package/dist/node.cjs +2 -3
  36. package/dist/node.d.cts +0 -1
  37. package/dist/{node.d.ts → node.d.mts} +1 -2
  38. package/dist/{node.js → node.mjs} +1 -2
  39. package/dist/route.cjs +7 -0
  40. package/dist/route.mjs +7 -0
  41. package/dist/runtime.cjs +42 -0
  42. package/dist/runtime.d.cts +21 -0
  43. package/dist/runtime.d.mts +21 -0
  44. package/dist/runtime.mjs +40 -0
  45. package/dist/schema/v0_21_0.cjs +1 -5
  46. package/dist/schema/{v0_21_0.js → v0_21_0.mjs} +1 -3
  47. package/dist/schema/v0_29_0.cjs +24 -0
  48. package/dist/schema/v0_29_0.mjs +24 -0
  49. package/dist/types/{index.d.ts → index.d.mts} +1 -1
  50. package/package.json +18 -18
  51. package/src/db/index.spec.ts +64 -29
  52. package/src/db/index.ts +55 -35
  53. package/src/db/ormCore.ts +438 -210
  54. package/src/db/ormUpdateCheck.bench.ts +261 -0
  55. package/src/db/pluginCore.ts +298 -49
  56. package/src/db/pluginUpdateCheck.bench.ts +250 -0
  57. package/src/db/types.ts +52 -27
  58. package/src/{handler-standalone-integration.spec.ts → handler-standalone.integration.spec.ts} +106 -0
  59. package/src/handler.spec.ts +156 -0
  60. package/src/handler.ts +296 -77
  61. package/src/internalRouter.ts +104 -0
  62. package/src/route.ts +7 -0
  63. package/src/runtime.spec.ts +277 -0
  64. package/src/runtime.ts +121 -0
  65. package/src/schema/v0_29_0.ts +26 -0
  66. package/dist/_virtual/rolldown_runtime.cjs +0 -25
  67. package/dist/adapters/drizzle.js +0 -3
  68. package/dist/adapters/kysely.js +0 -3
  69. package/dist/adapters/mongodb.js +0 -3
  70. package/dist/adapters/prisma.js +0 -3
  71. package/dist/db/index.d.ts +0 -27
  72. package/dist/db/index.js +0 -36
  73. package/dist/db/ormCore.d.ts +0 -26
  74. package/dist/db/ormCore.js +0 -273
  75. package/dist/db/pluginCore.js +0 -69
  76. package/dist/db/types.d.ts +0 -31
  77. package/dist/handler.d.ts +0 -37
  78. package/dist/handler.js +0 -146
  79. package/dist/index.d.ts +0 -5
  80. package/dist/index.js +0 -5
  81. /package/dist/adapters/{drizzle.d.ts → drizzle.d.mts} +0 -0
  82. /package/dist/adapters/{kysely.d.ts → kysely.d.mts} +0 -0
  83. /package/dist/adapters/{mongodb.d.ts → mongodb.d.mts} +0 -0
  84. /package/dist/adapters/{prisma.d.ts → prisma.d.mts} +0 -0
package/package.json CHANGED
@@ -1,37 +1,41 @@
1
1
  {
2
2
  "name": "@hot-updater/server",
3
- "version": "0.27.1",
3
+ "version": "0.29.0",
4
4
  "type": "module",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "sideEffects": false,
7
7
  "main": "./dist/index.cjs",
8
- "module": "./dist/index.js",
8
+ "module": "./dist/index.mjs",
9
9
  "types": "./dist/index.d.cts",
10
10
  "exports": {
11
11
  ".": {
12
- "import": "./dist/index.js",
12
+ "import": "./dist/index.mjs",
13
13
  "require": "./dist/index.cjs"
14
14
  },
15
15
  "./adapters/drizzle": {
16
- "import": "./dist/adapters/drizzle.js",
16
+ "import": "./dist/adapters/drizzle.mjs",
17
17
  "require": "./dist/adapters/drizzle.cjs"
18
18
  },
19
19
  "./adapters/kysely": {
20
- "import": "./dist/adapters/kysely.js",
20
+ "import": "./dist/adapters/kysely.mjs",
21
21
  "require": "./dist/adapters/kysely.cjs"
22
22
  },
23
23
  "./adapters/mongodb": {
24
- "import": "./dist/adapters/mongodb.js",
24
+ "import": "./dist/adapters/mongodb.mjs",
25
25
  "require": "./dist/adapters/mongodb.cjs"
26
26
  },
27
27
  "./adapters/prisma": {
28
- "import": "./dist/adapters/prisma.js",
28
+ "import": "./dist/adapters/prisma.mjs",
29
29
  "require": "./dist/adapters/prisma.cjs"
30
30
  },
31
31
  "./node": {
32
- "import": "./dist/node.js",
32
+ "import": "./dist/node.mjs",
33
33
  "require": "./dist/node.cjs"
34
34
  },
35
+ "./runtime": {
36
+ "import": "./dist/runtime.mjs",
37
+ "require": "./dist/runtime.cjs"
38
+ },
35
39
  "./package.json": "./package.json"
36
40
  },
37
41
  "files": [
@@ -49,25 +53,21 @@
49
53
  "fumadb": "0.2.2",
50
54
  "rou3": "0.7.9",
51
55
  "semver": "^7.7.2",
52
- "@hot-updater/plugin-core": "0.27.1",
53
- "@hot-updater/js": "0.27.1",
54
- "@hot-updater/core": "0.27.1"
56
+ "@hot-updater/core": "0.29.0",
57
+ "@hot-updater/plugin-core": "0.29.0",
58
+ "@hot-updater/js": "0.29.0"
55
59
  },
56
60
  "devDependencies": {
57
61
  "@electric-sql/pglite": "^0.2.17",
58
62
  "@types/node": "^20",
59
63
  "@types/semver": "^7.5.8",
60
- "execa": "^9.5.2",
64
+ "execa": "9.5.2",
61
65
  "kysely": "^0.28.9",
62
66
  "kysely-pglite-dialect": "^1.2.0",
63
67
  "msw": "^2.7.0",
64
68
  "uuidv7": "^1.0.2",
65
- "@hot-updater/aws": "0.27.1",
66
- "@hot-updater/cloudflare": "0.27.1",
67
- "@hot-updater/firebase": "0.27.1",
68
- "@hot-updater/standalone": "0.27.1",
69
- "@hot-updater/supabase": "0.27.1",
70
- "@hot-updater/test-utils": "0.27.1"
69
+ "@hot-updater/test-utils": "0.29.0",
70
+ "@hot-updater/standalone": "0.29.0"
71
71
  },
72
72
  "scripts": {
73
73
  "build": "tsdown",
@@ -1,11 +1,14 @@
1
1
  import { PGlite } from "@electric-sql/pglite";
2
- import { s3Storage } from "@hot-updater/aws";
3
- import { r2Storage } from "@hot-updater/cloudflare";
4
2
  import type { Bundle, GetBundlesArgs, UpdateInfo } from "@hot-updater/core";
5
3
  import { NIL_UUID } from "@hot-updater/core";
6
- import { firebaseStorage } from "@hot-updater/firebase";
7
- import { supabaseStorage } from "@hot-updater/supabase";
8
- import { setupGetUpdateInfoTestSuite } from "@hot-updater/test-utils";
4
+ import type {
5
+ StoragePlugin,
6
+ StorageResolveContext,
7
+ } from "@hot-updater/plugin-core";
8
+ import {
9
+ setupBundleMethodsTestSuite,
10
+ setupGetUpdateInfoTestSuite,
11
+ } from "@hot-updater/test-utils";
9
12
  import { Kysely } from "kysely";
10
13
  import { PGliteDialect } from "kysely-pglite-dialect";
11
14
  import {
@@ -21,6 +24,28 @@ import {
21
24
  import { kyselyAdapter } from "../adapters/kysely";
22
25
  import { createHotUpdater } from "./index";
23
26
 
27
+ function createTestStoragePlugin(
28
+ protocol: string,
29
+ resolveFileUrl: (
30
+ storageUri: string,
31
+ context?: StorageResolveContext,
32
+ ) => string,
33
+ ): StoragePlugin {
34
+ return {
35
+ name: `${protocol}TestStorage`,
36
+ supportedProtocol: protocol,
37
+ async upload(key) {
38
+ return {
39
+ storageUri: `${protocol}://test-bucket/${key}`,
40
+ };
41
+ },
42
+ async delete() {},
43
+ async getDownloadUrl(storageUri, context) {
44
+ return { fileUrl: resolveFileUrl(storageUri, context) };
45
+ },
46
+ };
47
+ }
48
+
24
49
  describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
25
50
  const db = new PGlite();
26
51
 
@@ -32,27 +57,29 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
32
57
  provider: "postgresql",
33
58
  }),
34
59
  storages: [
35
- s3Storage({
36
- region: "us-east-1",
37
- credentials: {
38
- accessKeyId: "test-access-key",
39
- secretAccessKey: "test-secret-key",
40
- },
41
- bucketName: "test-bucket",
42
- }),
43
- r2Storage({
44
- cloudflareApiToken: "test-token",
45
- accountId: "test-account-id",
46
- bucketName: "test-bucket",
47
- }),
48
- supabaseStorage({
49
- supabaseUrl: "https://test.supabase.co",
50
- supabaseAnonKey: "test-anon-key",
51
- bucketName: "test-bucket",
52
- }),
53
- firebaseStorage({
54
- storageBucket: "test-bucket.appspot.com",
55
- }),
60
+ createTestStoragePlugin("s3", (storageUri) =>
61
+ storageUri
62
+ .replace("s3://", "https://s3.example.com/")
63
+ .replace(/([^:]\/)\/+/g, "$1"),
64
+ ),
65
+ createTestStoragePlugin("r2", (storageUri) =>
66
+ storageUri
67
+ .replace("r2://", "https://r2.example.com/")
68
+ .replace(/([^:]\/)\/+/g, "$1"),
69
+ ),
70
+ createTestStoragePlugin("supabase-storage", (storageUri) =>
71
+ storageUri
72
+ .replace(
73
+ "supabase-storage://",
74
+ "https://supabase.example.com/storage/v1/object/sign/",
75
+ )
76
+ .replace(/([^:]\/)\/+/g, "$1"),
77
+ ),
78
+ createTestStoragePlugin("gs", (storageUri) =>
79
+ storageUri
80
+ .replace("gs://", "https://firebase.example.com/")
81
+ .replace(/([^:]\/)\/+/g, "$1"),
82
+ ),
56
83
  ],
57
84
  });
58
85
 
@@ -87,6 +114,14 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
87
114
  };
88
115
 
89
116
  setupGetUpdateInfoTestSuite({ getUpdateInfo });
117
+ setupBundleMethodsTestSuite({
118
+ getBundleById: hotUpdater.getBundleById.bind(hotUpdater),
119
+ getChannels: hotUpdater.getChannels.bind(hotUpdater),
120
+ insertBundle: hotUpdater.insertBundle.bind(hotUpdater),
121
+ getBundles: hotUpdater.getBundles.bind(hotUpdater),
122
+ updateBundleById: hotUpdater.updateBundleById.bind(hotUpdater),
123
+ deleteBundleById: hotUpdater.deleteBundleById.bind(hotUpdater),
124
+ });
90
125
 
91
126
  describe("getBundleById", () => {
92
127
  it("should retrieve bundle by id without Prisma validation errors", async () => {
@@ -223,7 +258,7 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
223
258
 
224
259
  expect(updateInfo).not.toBeNull();
225
260
  expect(updateInfo?.fileUrl).toBe(
226
- "https://test-bucket.s3.us-east-1.amazonaws.com/bundles/bundle.zip?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=test-access-key%2F20251015%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251015T122100Z&X-Amz-Expires=3600&X-Amz-Signature=4fa782e86a842ce2eacbfa6534d1f5d5145d733092959cf6ad755cc306bbe98e&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject",
261
+ "https://s3.example.com/test-bucket/bundles/bundle.zip",
227
262
  );
228
263
  });
229
264
 
@@ -253,7 +288,7 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
253
288
 
254
289
  expect(updateInfo).not.toBeNull();
255
290
  expect(updateInfo?.fileUrl).toBe(
256
- "https://bundle.s3.us-east-1.amazonaws.com/bundle.zip?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=test-access-key%2F20251015%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251015T122100Z&X-Amz-Expires=3600&X-Amz-Signature=b83d9cfc9bd23275e5eb3baf792776fd7b49730f3aa2f5172d067c9dfb10cd94&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject",
291
+ "https://s3.example.com/bundle/bundle.zip",
257
292
  );
258
293
  });
259
294
 
@@ -322,7 +357,7 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
322
357
 
323
358
  expect(updateInfo).not.toBeNull();
324
359
  expect(updateInfo?.fileUrl).toBe(
325
- "https://test-bucket.s3.us-east-1.amazonaws.com/fp-bundle.zip?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=test-access-key%2F20251015%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251015T122100Z&X-Amz-Expires=3600&X-Amz-Signature=d70e9b699dccbb51cf32f3e5b7912f2567d38f7e508b1f30091a8fee0d0abb65&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject",
360
+ "https://s3.example.com/test-bucket/fp-bundle.zip",
326
361
  );
327
362
  });
328
363
  });
package/src/db/index.ts CHANGED
@@ -1,5 +1,9 @@
1
- import type { StoragePlugin } from "@hot-updater/plugin-core";
2
- import { createHandler } from "../handler";
1
+ import type {
2
+ HotUpdaterContext,
3
+ StoragePlugin,
4
+ } from "@hot-updater/plugin-core";
5
+ import { createHandler, type HandlerRoutes } from "../handler";
6
+ import { normalizeBasePath } from "../route";
3
7
  import {
4
8
  createOrmDatabaseCore,
5
9
  type HotUpdaterClient,
@@ -17,41 +21,45 @@ import {
17
21
  export type { HotUpdaterClient, Migrator } from "./ormCore";
18
22
  export { HotUpdaterDB } from "./ormCore";
19
23
 
20
- type OrmCore = ReturnType<typeof createOrmDatabaseCore>;
21
- type PluginCore = ReturnType<typeof createPluginDatabaseCore>;
22
- type HotUpdaterCoreInternal = OrmCore | PluginCore;
23
-
24
- export type HotUpdaterAPI = DatabaseAPI & {
25
- handler: (request: Request) => Promise<Response>;
24
+ export type HotUpdaterAPI<TContext = unknown> = DatabaseAPI<TContext> & {
25
+ basePath: string;
26
+ handler: (
27
+ request: Request,
28
+ context?: HotUpdaterContext<TContext>,
29
+ ) => Promise<Response>;
26
30
  adapterName: string;
27
31
  createMigrator: () => Migrator;
28
32
  generateSchema: HotUpdaterClient["generateSchema"];
29
33
  };
30
34
 
31
- interface HotUpdaterOptions {
32
- database: DatabaseAdapter;
35
+ export interface CreateHotUpdaterOptions<TContext = unknown> {
36
+ database: DatabaseAdapter<TContext>;
33
37
  /**
34
38
  * Storage plugins for handling file uploads and downloads.
35
39
  */
36
- storages?: (StoragePlugin | StoragePluginFactory)[];
40
+ storages?: (StoragePlugin<TContext> | StoragePluginFactory<TContext>)[];
37
41
  /**
38
42
  * @deprecated Use `storages` instead. This field will be removed in a future version.
39
43
  */
40
- storagePlugins?: (StoragePlugin | StoragePluginFactory)[];
44
+ storagePlugins?: (StoragePlugin<TContext> | StoragePluginFactory<TContext>)[];
41
45
  basePath?: string;
42
46
  cwd?: string;
47
+ routes?: HandlerRoutes;
43
48
  }
44
49
 
45
- export function createHotUpdater(options: HotUpdaterOptions): HotUpdaterAPI {
50
+ export function createHotUpdater<TContext = unknown>(
51
+ options: CreateHotUpdaterOptions<TContext>,
52
+ ): HotUpdaterAPI<TContext> {
53
+ const basePath = normalizeBasePath(options.basePath ?? "/api");
54
+
46
55
  // Initialize storage plugins - call factories if they are functions
47
- const storagePlugins = (
48
- options?.storages ??
49
- options?.storagePlugins ??
50
- []
51
- ).map((plugin) => (typeof plugin === "function" ? plugin() : plugin));
56
+ const storagePlugins = (options.storages ?? options.storagePlugins ?? []).map(
57
+ (plugin) => (typeof plugin === "function" ? plugin() : plugin),
58
+ );
52
59
 
53
- const resolveFileUrl = async (
60
+ const resolveStoragePluginUrl = async (
54
61
  storageUri: string | null,
62
+ context?: HotUpdaterContext<TContext>,
55
63
  ): Promise<string | null> => {
56
64
  if (!storageUri) {
57
65
  return null;
@@ -66,35 +74,47 @@ export function createHotUpdater(options: HotUpdaterOptions): HotUpdaterAPI {
66
74
  if (!plugin) {
67
75
  throw new Error(`No storage plugin for protocol: ${protocol}`);
68
76
  }
69
- const { fileUrl } = await plugin.getDownloadUrl(storageUri);
77
+ const { fileUrl } = await plugin.getDownloadUrl(storageUri, context);
70
78
  if (!fileUrl) {
71
79
  throw new Error("Storage plugin returned empty fileUrl");
72
80
  }
73
81
  return fileUrl;
74
82
  };
75
83
 
76
- let core: HotUpdaterCoreInternal;
84
+ const resolveFileUrl = async (
85
+ storageUri: string | null,
86
+ context?: HotUpdaterContext<TContext>,
87
+ ) => {
88
+ return resolveStoragePluginUrl(storageUri, context);
89
+ };
77
90
 
78
91
  const database = options.database;
79
92
 
80
- if (isDatabasePluginFactory(database) || isDatabasePlugin(database)) {
81
- const plugin = isDatabasePluginFactory(database) ? database() : database;
82
- core = createPluginDatabaseCore(plugin, resolveFileUrl);
83
- } else {
84
- core = createOrmDatabaseCore({
85
- database,
86
- resolveFileUrl,
87
- });
88
- }
93
+ const core =
94
+ isDatabasePluginFactory(database) || isDatabasePlugin(database)
95
+ ? createPluginDatabaseCore<TContext>(
96
+ isDatabasePluginFactory(database) ? database() : database,
97
+ resolveFileUrl,
98
+ )
99
+ : createOrmDatabaseCore<TContext>({
100
+ database,
101
+ resolveFileUrl,
102
+ });
89
103
 
90
- return {
104
+ const api = {
91
105
  ...core.api,
92
- handler: createHandler(
93
- core.api,
94
- options?.basePath ? { basePath: options.basePath } : {},
95
- ),
106
+ handler: createHandler(core.api, {
107
+ basePath,
108
+ routes: options.routes,
109
+ }),
96
110
  adapterName: core.adapterName,
97
111
  createMigrator: core.createMigrator,
98
112
  generateSchema: core.generateSchema,
99
113
  };
114
+
115
+ return {
116
+ ...api,
117
+ basePath,
118
+ handler: api.handler,
119
+ };
100
120
  }