@hot-updater/server 0.34.0 → 0.35.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/{runtime.cjs → createHotUpdaterCore.cjs} +29 -14
- package/dist/createHotUpdaterCore.d.cts +25 -0
- package/dist/createHotUpdaterCore.d.mts +25 -0
- package/dist/{runtime.mjs → createHotUpdaterCore.mjs} +29 -12
- package/dist/db/createBundleDiff.cjs +1 -1
- package/dist/db/createBundleDiff.mjs +1 -1
- package/dist/db/index.cjs +23 -43
- package/dist/db/index.d.cts +6 -25
- package/dist/db/index.d.mts +6 -25
- package/dist/db/index.mjs +18 -42
- package/dist/db/schemaGenerators.cjs +1 -1
- package/dist/db/schemaGenerators.mjs +1 -1
- package/dist/index.cjs +2 -6
- package/dist/index.d.cts +3 -6
- package/dist/index.d.mts +3 -6
- package/dist/index.mjs +2 -4
- package/dist/node.cjs +2 -0
- package/dist/node.d.cts +7 -3
- package/dist/node.d.mts +7 -3
- package/dist/node.mjs +2 -1
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/types/index.d.cts +1 -1
- package/dist/types/index.d.mts +1 -1
- package/package.json +11 -11
- package/src/adapters/drizzle.spec.ts +3 -2
- package/src/adapters/kysely.spec.ts +1 -1
- package/src/{runtime.ts → createHotUpdaterCore.ts} +73 -23
- package/src/db/index.spec.ts +15 -14
- package/src/db/index.ts +34 -106
- package/src/handler-standalone.integration.spec.ts +3 -2
- package/src/index.ts +9 -3
- package/src/node.spec.ts +50 -0
- package/src/node.ts +7 -3
- package/src/runtime.spec.ts +62 -1
- package/src/types/index.ts +1 -1
- package/dist/runtime.d.cts +0 -22
- package/dist/runtime.d.mts +0 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "React Native OTA solution for self-hosted",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -28,14 +28,14 @@
|
|
|
28
28
|
"import": "./dist/adapters/prisma.mjs",
|
|
29
29
|
"require": "./dist/adapters/prisma.cjs"
|
|
30
30
|
},
|
|
31
|
+
"./db": {
|
|
32
|
+
"import": "./dist/db/index.mjs",
|
|
33
|
+
"require": "./dist/db/index.cjs"
|
|
34
|
+
},
|
|
31
35
|
"./node": {
|
|
32
36
|
"import": "./dist/node.mjs",
|
|
33
37
|
"require": "./dist/node.cjs"
|
|
34
38
|
},
|
|
35
|
-
"./runtime": {
|
|
36
|
-
"import": "./dist/runtime.mjs",
|
|
37
|
-
"require": "./dist/runtime.cjs"
|
|
38
|
-
},
|
|
39
39
|
"./package.json": "./package.json"
|
|
40
40
|
},
|
|
41
41
|
"files": [
|
|
@@ -55,10 +55,10 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"rou3": "0.7.9",
|
|
57
57
|
"semver": "^7.7.2",
|
|
58
|
-
"@hot-updater/bsdiff": "0.
|
|
59
|
-
"@hot-updater/core": "0.
|
|
60
|
-
"@hot-updater/
|
|
61
|
-
"@hot-updater/
|
|
58
|
+
"@hot-updater/bsdiff": "0.35.1",
|
|
59
|
+
"@hot-updater/core": "0.35.1",
|
|
60
|
+
"@hot-updater/plugin-core": "0.35.1",
|
|
61
|
+
"@hot-updater/js": "0.35.1"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
64
|
"@prisma/client": "*",
|
|
@@ -91,8 +91,8 @@
|
|
|
91
91
|
"mongodb": "6.20.0",
|
|
92
92
|
"msw": "^2.7.0",
|
|
93
93
|
"uuidv7": "^1.0.2",
|
|
94
|
-
"@hot-updater/standalone": "0.
|
|
95
|
-
"@hot-updater/test-utils": "0.
|
|
94
|
+
"@hot-updater/standalone": "0.35.1",
|
|
95
|
+
"@hot-updater/test-utils": "0.35.1"
|
|
96
96
|
},
|
|
97
97
|
"scripts": {
|
|
98
98
|
"build": "tsdown",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { generateSchema } from "../db";
|
|
4
|
+
import { createHotUpdater } from "../index";
|
|
4
5
|
import { drizzleAdapter } from "./drizzle";
|
|
5
6
|
|
|
6
7
|
describe("drizzleAdapter", () => {
|
|
@@ -16,7 +17,7 @@ describe("drizzleAdapter", () => {
|
|
|
16
17
|
}),
|
|
17
18
|
});
|
|
18
19
|
|
|
19
|
-
const schema =
|
|
20
|
+
const schema = generateSchema(hotUpdater, "latest");
|
|
20
21
|
|
|
21
22
|
expect(schema.path).toBe("hot-updater-schema.ts");
|
|
22
23
|
expect(schema.code).toContain("pgTable");
|
|
@@ -4,7 +4,7 @@ import { Kysely } from "kysely";
|
|
|
4
4
|
import { PGliteDialect } from "kysely-pglite-dialect";
|
|
5
5
|
import { afterEach, describe, expect, it } from "vitest";
|
|
6
6
|
|
|
7
|
-
import { createHotUpdater } from "../
|
|
7
|
+
import { createHotUpdater } from "../index";
|
|
8
8
|
import {
|
|
9
9
|
HOT_UPDATER_SCHEMA_VERSION,
|
|
10
10
|
HOT_UPDATER_SETTINGS_TABLE,
|
|
@@ -19,33 +19,70 @@ import { createHandler, type HandlerRoutes } from "./handler";
|
|
|
19
19
|
import { normalizeBasePath } from "./route";
|
|
20
20
|
import { createStorageAccess } from "./storageAccess";
|
|
21
21
|
|
|
22
|
-
export type
|
|
23
|
-
basePath: string;
|
|
24
|
-
handler: (
|
|
22
|
+
export type RuntimeHotUpdaterAPI<TContext = unknown> = DatabaseAPI<TContext> & {
|
|
23
|
+
readonly basePath: string;
|
|
24
|
+
readonly handler: (
|
|
25
25
|
request: Request,
|
|
26
26
|
context?: HotUpdaterContext<TContext>,
|
|
27
27
|
) => Promise<Response>;
|
|
28
|
-
adapterName: string;
|
|
28
|
+
readonly adapterName: string;
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
+
export type HotUpdaterAPI<TContext = unknown> = RuntimeHotUpdaterAPI<TContext>;
|
|
32
|
+
|
|
31
33
|
export interface CreateHotUpdaterOptions<TContext = unknown> {
|
|
32
|
-
database: DatabaseAdapter<TContext>;
|
|
33
|
-
storages?: (
|
|
34
|
+
readonly database: DatabaseAdapter<TContext>;
|
|
35
|
+
readonly storages?: readonly (
|
|
34
36
|
| RuntimeStoragePlugin<TContext>
|
|
35
37
|
| StoragePluginFactory<TContext>
|
|
36
38
|
)[];
|
|
37
|
-
|
|
39
|
+
/**
|
|
40
|
+
* @deprecated Use `storages` instead. This field will be removed in a future version.
|
|
41
|
+
*/
|
|
42
|
+
readonly storagePlugins?: readonly (
|
|
38
43
|
| RuntimeStoragePlugin<TContext>
|
|
39
44
|
| StoragePluginFactory<TContext>
|
|
40
45
|
)[];
|
|
41
|
-
basePath?: string;
|
|
42
|
-
cwd?: string;
|
|
43
|
-
routes?: HandlerRoutes;
|
|
46
|
+
readonly basePath?: string;
|
|
47
|
+
readonly cwd?: string;
|
|
48
|
+
readonly routes?: HandlerRoutes;
|
|
44
49
|
}
|
|
45
50
|
|
|
46
|
-
|
|
51
|
+
type PluginDatabaseCore<TContext> = {
|
|
52
|
+
readonly api: DatabaseAPI<TContext>;
|
|
53
|
+
readonly adapterName: string;
|
|
54
|
+
readonly createMigrator: () => never;
|
|
55
|
+
readonly generateSchema: () => never;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const hotUpdaterCoreMetadata = Symbol.for(
|
|
59
|
+
"@hot-updater/server/core-metadata",
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
export type HotUpdaterCoreMetadata<TContext = unknown> = {
|
|
63
|
+
readonly adapterCapabilities: DatabaseAdapterCapabilities;
|
|
64
|
+
readonly core: PluginDatabaseCore<TContext>;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type HotUpdaterCore<TContext = unknown> = {
|
|
68
|
+
readonly api: RuntimeHotUpdaterAPI<TContext>;
|
|
69
|
+
readonly adapterCapabilities: DatabaseAdapterCapabilities;
|
|
70
|
+
readonly core: PluginDatabaseCore<TContext>;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export function getHotUpdaterCoreMetadata<TContext = unknown>(
|
|
74
|
+
hotUpdater: RuntimeHotUpdaterAPI<TContext>,
|
|
75
|
+
): HotUpdaterCoreMetadata<TContext> | undefined {
|
|
76
|
+
return (
|
|
77
|
+
hotUpdater as RuntimeHotUpdaterAPI<TContext> & {
|
|
78
|
+
readonly [hotUpdaterCoreMetadata]?: HotUpdaterCoreMetadata<TContext>;
|
|
79
|
+
}
|
|
80
|
+
)[hotUpdaterCoreMetadata];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function createHotUpdaterCore<TContext = unknown>(
|
|
47
84
|
options: CreateHotUpdaterOptions<TContext>,
|
|
48
|
-
):
|
|
85
|
+
): HotUpdaterCore<TContext> {
|
|
49
86
|
const database = options.database;
|
|
50
87
|
const basePath = normalizeBasePath(options.basePath ?? "/api");
|
|
51
88
|
const storagePlugins = (options.storages ?? options.storagePlugins ?? []).map(
|
|
@@ -59,19 +96,17 @@ export function createHotUpdater<TContext = unknown>(
|
|
|
59
96
|
createStorageAccess(storagePlugins);
|
|
60
97
|
|
|
61
98
|
if (!isDatabasePluginFactory(database) && !isDatabasePlugin(database)) {
|
|
62
|
-
throw new Error(
|
|
63
|
-
"@hot-updater/server/runtime only supports database plugins.",
|
|
64
|
-
);
|
|
99
|
+
throw new Error("@hot-updater/server only supports database plugins.");
|
|
65
100
|
}
|
|
66
101
|
|
|
67
|
-
const
|
|
102
|
+
const adapterCapabilities = database as DatabaseAdapterCapabilities;
|
|
68
103
|
const plugin: DatabasePlugin<TContext> = isDatabasePluginFactory(database)
|
|
69
104
|
? database()
|
|
70
105
|
: database;
|
|
71
|
-
const adapterName =
|
|
106
|
+
const adapterName = adapterCapabilities.adapterName ?? plugin.name;
|
|
72
107
|
const assertSchemaReady = createSchemaReadinessChecker(
|
|
73
108
|
adapterName,
|
|
74
|
-
|
|
109
|
+
adapterCapabilities.createMigrator,
|
|
75
110
|
);
|
|
76
111
|
const core = createPluginDatabaseCore<TContext>(
|
|
77
112
|
() => plugin,
|
|
@@ -93,7 +128,7 @@ export function createHotUpdater<TContext = unknown>(
|
|
|
93
128
|
// Some framework adapters strip the mounted base path or pass extra
|
|
94
129
|
// bindings/execution context arguments. Ignore those extras here so the
|
|
95
130
|
// handler can still be mounted directly as a plain Request handler.
|
|
96
|
-
const handler:
|
|
131
|
+
const handler: RuntimeHotUpdaterAPI<TContext>["handler"] = (
|
|
97
132
|
request,
|
|
98
133
|
context,
|
|
99
134
|
...extraArgs: unknown[]
|
|
@@ -107,12 +142,27 @@ export function createHotUpdater<TContext = unknown>(
|
|
|
107
142
|
|
|
108
143
|
const api = {
|
|
109
144
|
basePath,
|
|
110
|
-
adapterName: core.adapterName,
|
|
145
|
+
adapterName: adapterCapabilities.adapterName ?? core.adapterName,
|
|
111
146
|
handler,
|
|
112
147
|
};
|
|
113
148
|
Object.defineProperties(api, Object.getOwnPropertyDescriptors(core.api));
|
|
114
|
-
|
|
149
|
+
Object.defineProperty(api, hotUpdaterCoreMetadata, {
|
|
150
|
+
enumerable: false,
|
|
151
|
+
value: {
|
|
152
|
+
adapterCapabilities,
|
|
153
|
+
core,
|
|
154
|
+
} satisfies HotUpdaterCoreMetadata<TContext>,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
api: api as RuntimeHotUpdaterAPI<TContext>,
|
|
159
|
+
adapterCapabilities,
|
|
160
|
+
core,
|
|
161
|
+
};
|
|
115
162
|
}
|
|
116
163
|
|
|
117
|
-
export
|
|
118
|
-
|
|
164
|
+
export function createHotUpdater<TContext = unknown>(
|
|
165
|
+
options: CreateHotUpdaterOptions<TContext>,
|
|
166
|
+
): RuntimeHotUpdaterAPI<TContext> {
|
|
167
|
+
return createHotUpdaterCore(options).api;
|
|
168
|
+
}
|
package/src/db/index.spec.ts
CHANGED
|
@@ -28,13 +28,14 @@ import { drizzleAdapter } from "../adapters/drizzle";
|
|
|
28
28
|
import { kyselyAdapter } from "../adapters/kysely";
|
|
29
29
|
import { mongoAdapter } from "../adapters/mongodb";
|
|
30
30
|
import { prismaAdapter } from "../adapters/prisma";
|
|
31
|
+
import { createHotUpdater } from "../index";
|
|
31
32
|
import { bundleToRow } from "./bundleRows";
|
|
32
33
|
import {
|
|
33
34
|
createSchemaMigrationSql,
|
|
34
35
|
createTableSql,
|
|
35
36
|
hotUpdaterSchemaVersions,
|
|
36
37
|
} from "./hotUpdaterSchema";
|
|
37
|
-
import {
|
|
38
|
+
import { createMigrator, generateSchema } from "./index";
|
|
38
39
|
import { generateDrizzleSchema } from "./schemaGenerators";
|
|
39
40
|
import type { DatabasePluginFactory, ORMProvider } from "./types";
|
|
40
41
|
|
|
@@ -340,7 +341,7 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
340
341
|
});
|
|
341
342
|
|
|
342
343
|
beforeAll(async () => {
|
|
343
|
-
const migrator =
|
|
344
|
+
const migrator = createMigrator(hotUpdater);
|
|
344
345
|
const result = await migrator.migrateToLatest({
|
|
345
346
|
mode: "from-schema",
|
|
346
347
|
updateSettings: true,
|
|
@@ -382,7 +383,7 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
382
383
|
|
|
383
384
|
describe("schema generation", () => {
|
|
384
385
|
it("includes relations, defaults, and indexes in Prisma output", () => {
|
|
385
|
-
const code =
|
|
386
|
+
const code = generateSchema(prismaSchemaHotUpdater, "latest").code;
|
|
386
387
|
|
|
387
388
|
expect(code).toContain('channel String @default("production")');
|
|
388
389
|
expect(code).toContain('metadata Json @default("{}")');
|
|
@@ -407,14 +408,14 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
407
408
|
});
|
|
408
409
|
|
|
409
410
|
it("omits the metadata JSON default for SQLite Prisma output", () => {
|
|
410
|
-
const code =
|
|
411
|
+
const code = generateSchema(sqlitePrismaSchemaHotUpdater, "latest").code;
|
|
411
412
|
|
|
412
413
|
expect(code).toContain("metadata Json");
|
|
413
414
|
expect(code).not.toContain('metadata Json @default("{}")');
|
|
414
415
|
});
|
|
415
416
|
|
|
416
417
|
it("generates ORM schema from the requested version snapshot", () => {
|
|
417
|
-
const code =
|
|
418
|
+
const code = generateSchema(prismaSchemaHotUpdater, "0.21.0").code;
|
|
418
419
|
|
|
419
420
|
expect(code).toContain('value String @default("0.21.0")');
|
|
420
421
|
expect(code).not.toContain("rollout_cohort_count");
|
|
@@ -422,7 +423,7 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
422
423
|
});
|
|
423
424
|
|
|
424
425
|
it("includes foreign keys and indexes in Drizzle output", () => {
|
|
425
|
-
const code =
|
|
426
|
+
const code = generateSchema(drizzleSchemaHotUpdater, "latest").code;
|
|
426
427
|
const bundlesBlock = code.match(
|
|
427
428
|
/export const bundles = [\s\S]*?(?=\n\nexport const bundle_patches = )/,
|
|
428
429
|
)?.[0];
|
|
@@ -526,7 +527,7 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
526
527
|
});
|
|
527
528
|
|
|
528
529
|
try {
|
|
529
|
-
const migrator =
|
|
530
|
+
const migrator = createMigrator(migrationHotUpdater);
|
|
530
531
|
const result = await migrator.migrateToLatest({
|
|
531
532
|
mode: "from-schema",
|
|
532
533
|
updateSettings: false,
|
|
@@ -593,7 +594,7 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
593
594
|
values ('version', '0.21.0');
|
|
594
595
|
`);
|
|
595
596
|
|
|
596
|
-
const migrator =
|
|
597
|
+
const migrator = createMigrator(migrationHotUpdater);
|
|
597
598
|
const result = await migrator.migrateToLatest({
|
|
598
599
|
mode: "from-schema",
|
|
599
600
|
updateSettings: true,
|
|
@@ -639,7 +640,7 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
639
640
|
});
|
|
640
641
|
|
|
641
642
|
try {
|
|
642
|
-
const migrator =
|
|
643
|
+
const migrator = createMigrator(migrationHotUpdater);
|
|
643
644
|
const result = await migrator.migrateToLatest({
|
|
644
645
|
mode: "from-schema",
|
|
645
646
|
updateSettings: false,
|
|
@@ -671,7 +672,7 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
671
672
|
});
|
|
672
673
|
|
|
673
674
|
try {
|
|
674
|
-
const migrator =
|
|
675
|
+
const migrator = createMigrator(migrationHotUpdater);
|
|
675
676
|
const result = await migrator.migrateToLatest({
|
|
676
677
|
mode: "from-schema",
|
|
677
678
|
updateSettings: false,
|
|
@@ -703,9 +704,9 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
703
704
|
const mongoHotUpdater = createHotUpdater({
|
|
704
705
|
database: mongoAdapter({ client }),
|
|
705
706
|
});
|
|
706
|
-
const result = await mongoHotUpdater
|
|
707
|
-
|
|
708
|
-
|
|
707
|
+
const result = await createMigrator(mongoHotUpdater).migrateToLatest({
|
|
708
|
+
mode: "from-schema",
|
|
709
|
+
});
|
|
709
710
|
|
|
710
711
|
expect(result.operations).toEqual(
|
|
711
712
|
expect.arrayContaining([
|
|
@@ -742,7 +743,7 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
742
743
|
|
|
743
744
|
try {
|
|
744
745
|
await expect(
|
|
745
|
-
|
|
746
|
+
createMigrator(migrationHotUpdater).migrateToLatest({
|
|
746
747
|
mode: "from-database",
|
|
747
748
|
}),
|
|
748
749
|
).rejects.toThrow(
|
package/src/db/index.ts
CHANGED
|
@@ -1,119 +1,47 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
DatabasePlugin,
|
|
3
|
-
HotUpdaterContext,
|
|
4
|
-
RuntimeStoragePlugin,
|
|
5
|
-
} from "@hot-updater/plugin-core";
|
|
6
|
-
import { assertRuntimeStoragePlugin } from "@hot-updater/plugin-core";
|
|
7
|
-
|
|
8
|
-
export * from "./createBundleDiff";
|
|
9
|
-
import { createHandler, type HandlerRoutes } from "../handler";
|
|
10
|
-
import { normalizeBasePath } from "../route";
|
|
11
|
-
import { createStorageAccess } from "../storageAccess";
|
|
12
|
-
import { createPluginDatabaseCore } from "./pluginCore";
|
|
13
|
-
import { generateSchemaFromHotUpdaterSchema } from "./schemaGenerators";
|
|
14
|
-
import { createSchemaReadinessChecker } from "./schemaReadiness";
|
|
15
1
|
import {
|
|
16
|
-
|
|
17
|
-
type
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
isDatabasePluginFactory,
|
|
22
|
-
type StoragePluginFactory,
|
|
23
|
-
} from "./types";
|
|
2
|
+
getHotUpdaterCoreMetadata,
|
|
3
|
+
type RuntimeHotUpdaterAPI,
|
|
4
|
+
} from "../createHotUpdaterCore";
|
|
5
|
+
import { generateSchemaFromHotUpdaterSchema } from "./schemaGenerators";
|
|
6
|
+
import { type Migrator, type SchemaGenerator } from "./types";
|
|
24
7
|
|
|
8
|
+
export * from "./createBundleDiff";
|
|
25
9
|
export type { Migrator, SchemaGenerator } from "./types";
|
|
26
10
|
export { HotUpdaterSchemaMigrationRequiredError } from "./schemaReadiness";
|
|
27
11
|
export { HOT_UPDATER_SERVER_VERSION } from "../version";
|
|
28
12
|
|
|
29
|
-
export type
|
|
30
|
-
|
|
31
|
-
handler: (
|
|
32
|
-
request: Request,
|
|
33
|
-
context?: HotUpdaterContext<TContext>,
|
|
34
|
-
) => Promise<Response>;
|
|
35
|
-
adapterName: string;
|
|
36
|
-
createMigrator: () => Migrator;
|
|
37
|
-
generateSchema: SchemaGenerator;
|
|
13
|
+
export type HotUpdaterDBTarget = {
|
|
14
|
+
readonly adapterName: string;
|
|
38
15
|
};
|
|
39
16
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
* Storage plugins for handling file uploads and downloads.
|
|
44
|
-
*/
|
|
45
|
-
storages?: (
|
|
46
|
-
| RuntimeStoragePlugin<TContext>
|
|
47
|
-
| StoragePluginFactory<TContext>
|
|
48
|
-
)[];
|
|
49
|
-
/**
|
|
50
|
-
* @deprecated Use `storages` instead. This field will be removed in a future version.
|
|
51
|
-
*/
|
|
52
|
-
storagePlugins?: (
|
|
53
|
-
| RuntimeStoragePlugin<TContext>
|
|
54
|
-
| StoragePluginFactory<TContext>
|
|
55
|
-
)[];
|
|
56
|
-
basePath?: string;
|
|
57
|
-
cwd?: string;
|
|
58
|
-
routes?: HandlerRoutes;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function createHotUpdater<TContext = unknown>(
|
|
62
|
-
options: CreateHotUpdaterOptions<TContext>,
|
|
63
|
-
): HotUpdaterAPI<TContext> {
|
|
64
|
-
const basePath = normalizeBasePath(options.basePath ?? "/api");
|
|
65
|
-
|
|
66
|
-
// Initialize storage plugins - call factories if they are functions
|
|
67
|
-
const storagePlugins = (options.storages ?? options.storagePlugins ?? []).map(
|
|
68
|
-
(plugin) => {
|
|
69
|
-
const storagePlugin = typeof plugin === "function" ? plugin() : plugin;
|
|
70
|
-
assertRuntimeStoragePlugin(storagePlugin);
|
|
71
|
-
return storagePlugin;
|
|
72
|
-
},
|
|
17
|
+
const getDBMetadata = (hotUpdater: HotUpdaterDBTarget) => {
|
|
18
|
+
const metadata = getHotUpdaterCoreMetadata(
|
|
19
|
+
hotUpdater as RuntimeHotUpdaterAPI,
|
|
73
20
|
);
|
|
74
|
-
|
|
75
|
-
|
|
21
|
+
if (!metadata) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
"Database tooling requires a hotUpdater instance created by @hot-updater/server.",
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return metadata;
|
|
27
|
+
};
|
|
76
28
|
|
|
77
|
-
|
|
29
|
+
export function createMigrator(hotUpdater: HotUpdaterDBTarget): Migrator {
|
|
30
|
+
const { adapterCapabilities, core } = getDBMetadata(hotUpdater);
|
|
31
|
+
return (adapterCapabilities.createMigrator ?? core.createMigrator)();
|
|
32
|
+
}
|
|
78
33
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
isDatabasePluginFactory(database)
|
|
92
|
-
? {
|
|
93
|
-
createMutationPlugin: () => database(),
|
|
94
|
-
beforeOperation: assertSchemaReady,
|
|
95
|
-
readStorageText,
|
|
96
|
-
}
|
|
97
|
-
: { beforeOperation: assertSchemaReady, readStorageText },
|
|
34
|
+
export function generateSchema(
|
|
35
|
+
hotUpdater: HotUpdaterDBTarget,
|
|
36
|
+
...args: Parameters<SchemaGenerator>
|
|
37
|
+
): ReturnType<SchemaGenerator> {
|
|
38
|
+
const { adapterCapabilities, core } = getDBMetadata(hotUpdater);
|
|
39
|
+
const schemaGenerator =
|
|
40
|
+
adapterCapabilities.generateSchema ?? core.generateSchema;
|
|
41
|
+
return generateSchemaFromHotUpdaterSchema(
|
|
42
|
+
hotUpdater.adapterName,
|
|
43
|
+
adapterCapabilities.provider,
|
|
44
|
+
args[0],
|
|
45
|
+
schemaGenerator(...args),
|
|
98
46
|
);
|
|
99
|
-
|
|
100
|
-
const generateSchema = capabilities.generateSchema ?? core.generateSchema;
|
|
101
|
-
const api = {
|
|
102
|
-
basePath,
|
|
103
|
-
adapterName: capabilities.adapterName ?? core.adapterName,
|
|
104
|
-
createMigrator: capabilities.createMigrator ?? core.createMigrator,
|
|
105
|
-
generateSchema: (...args: Parameters<SchemaGenerator>) =>
|
|
106
|
-
generateSchemaFromHotUpdaterSchema(
|
|
107
|
-
api.adapterName,
|
|
108
|
-
capabilities.provider,
|
|
109
|
-
args[0],
|
|
110
|
-
generateSchema(...args),
|
|
111
|
-
),
|
|
112
|
-
handler: createHandler(core.api, {
|
|
113
|
-
basePath,
|
|
114
|
-
routes: options.routes,
|
|
115
|
-
}),
|
|
116
|
-
};
|
|
117
|
-
Object.defineProperties(api, Object.getOwnPropertyDescriptors(core.api));
|
|
118
|
-
return api as HotUpdaterAPI<TContext>;
|
|
119
47
|
}
|
|
@@ -11,7 +11,8 @@ import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
|
|
|
11
11
|
|
|
12
12
|
import { standaloneRepository } from "../../../plugins/standalone/src";
|
|
13
13
|
import { kyselyAdapter } from "./adapters/kysely";
|
|
14
|
-
import {
|
|
14
|
+
import { createMigrator } from "./db";
|
|
15
|
+
import { createHotUpdater } from "./index";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Integration tests between @hot-updater/server handler and @hot-updater/standalone repository
|
|
@@ -46,7 +47,7 @@ const server = setupServer();
|
|
|
46
47
|
|
|
47
48
|
beforeAll(async () => {
|
|
48
49
|
// Initialize database
|
|
49
|
-
const migrator =
|
|
50
|
+
const migrator = createMigrator(api);
|
|
50
51
|
const result = await migrator.migrateToLatest({
|
|
51
52
|
mode: "from-schema",
|
|
52
53
|
updateSettings: true,
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
1
|
+
export { createHandler } from "./handler";
|
|
2
|
+
export type { HandlerAPI, HandlerOptions, HandlerRoutes } from "./handler";
|
|
3
|
+
export { createHotUpdater } from "./createHotUpdaterCore";
|
|
4
|
+
export type {
|
|
5
|
+
CreateHotUpdaterOptions,
|
|
6
|
+
HotUpdaterAPI,
|
|
7
|
+
RuntimeHotUpdaterAPI,
|
|
8
|
+
} from "./createHotUpdaterCore";
|
|
3
9
|
export * from "./types";
|
|
4
|
-
export
|
|
10
|
+
export { HOT_UPDATER_SERVER_VERSION } from "./version";
|
package/src/node.spec.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { toNodeHandler } from "./node";
|
|
4
|
+
|
|
5
|
+
describe("server node entry", () => {
|
|
6
|
+
it("converts a Web Request handler to Node middleware", async () => {
|
|
7
|
+
const hotUpdater = {
|
|
8
|
+
handler: async (request: Request) =>
|
|
9
|
+
Response.json({
|
|
10
|
+
method: request.method,
|
|
11
|
+
pathname: new URL(request.url).pathname,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
const middleware = toNodeHandler(hotUpdater);
|
|
15
|
+
const headers = new Map<string, string | string[]>();
|
|
16
|
+
const response = {
|
|
17
|
+
body: "",
|
|
18
|
+
statusCode: 0,
|
|
19
|
+
status(code: number) {
|
|
20
|
+
this.statusCode = code;
|
|
21
|
+
return this;
|
|
22
|
+
},
|
|
23
|
+
setHeader(name: string, value: string | string[]) {
|
|
24
|
+
headers.set(name, value);
|
|
25
|
+
},
|
|
26
|
+
send(body: string) {
|
|
27
|
+
this.body = body;
|
|
28
|
+
},
|
|
29
|
+
end() {},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
await middleware(
|
|
33
|
+
{
|
|
34
|
+
method: "GET",
|
|
35
|
+
url: "/api/check",
|
|
36
|
+
headers: { host: "example.com" },
|
|
37
|
+
protocol: "https",
|
|
38
|
+
get: (name: string) => (name === "host" ? "example.com" : undefined),
|
|
39
|
+
},
|
|
40
|
+
response,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
expect(response.statusCode).toBe(200);
|
|
44
|
+
expect(headers.get("content-type")).toContain("application/json");
|
|
45
|
+
expect(JSON.parse(response.body)).toEqual({
|
|
46
|
+
method: "GET",
|
|
47
|
+
pathname: "/api/check",
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
package/src/node.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { HotUpdaterAPI } from "./types";
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Node.js request/response types (compatible with Express, Connect, etc.)
|
|
5
3
|
*/
|
|
@@ -21,6 +19,12 @@ interface NodeResponse {
|
|
|
21
19
|
[key: string]: unknown;
|
|
22
20
|
}
|
|
23
21
|
|
|
22
|
+
type HandlerHotUpdaterAPI = {
|
|
23
|
+
readonly handler: (request: Request) => Promise<Response>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { HOT_UPDATER_SERVER_VERSION } from "./version";
|
|
27
|
+
|
|
24
28
|
/**
|
|
25
29
|
* Converts a Hot Updater handler to a Node.js-compatible middleware
|
|
26
30
|
* Works with Express, Connect, and other frameworks using Node.js req/res
|
|
@@ -40,7 +44,7 @@ interface NodeResponse {
|
|
|
40
44
|
* ```
|
|
41
45
|
*/
|
|
42
46
|
export function toNodeHandler(
|
|
43
|
-
hotUpdater:
|
|
47
|
+
hotUpdater: HandlerHotUpdaterAPI,
|
|
44
48
|
): (req: any, res: any, next?: any) => Promise<void> {
|
|
45
49
|
return async (req: NodeRequest, res: NodeResponse) => {
|
|
46
50
|
try {
|