@hot-updater/supabase 1.0.0-rc.17 → 1.0.0-rc.18
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/edge.d.cts +1 -1
- package/dist/edge.d.mts +1 -1
- package/dist/iac/index.cjs +16 -38
- package/dist/iac/index.d.cts +3 -4
- package/dist/iac/index.d.mts +3 -4
- package/dist/iac/index.mjs +18 -39
- package/dist/index.cjs +8 -8
- package/dist/index.d.cts +4 -4
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +9 -9
- package/dist/{supabaseDatabase-CGDLVEvG.d.cts → supabaseDatabase-C0-IshLi.d.cts} +107 -26
- package/dist/{supabaseDatabase-CGDLVEvG.d.mts → supabaseDatabase-C0-IshLi.d.mts} +107 -26
- package/package.json +7 -7
- package/supabase/edge-functions/runtime.docker.integration.spec.ts +16 -14
- package/supabase/migrations/20260818000000_hot-updater_1.0.0.sql +43 -5
- package/supabase/migrations.spec.ts +19 -8
package/dist/edge.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as plugins, n as supabaseDatabase, t as SupabaseDatabaseConfig } from "./supabaseDatabase-
|
|
1
|
+
import { i as plugins, n as supabaseDatabase, t as SupabaseDatabaseConfig } from "./supabaseDatabase-C0-IshLi.cjs";
|
|
2
2
|
import { StoragePluginWith } from "@hot-updater/plugin-core";
|
|
3
3
|
//#region src/supabaseEdgeFunctionStorage.d.ts
|
|
4
4
|
interface SupabaseEdgeFunctionStorageConfig {
|
package/dist/edge.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as plugins, n as supabaseDatabase, t as SupabaseDatabaseConfig } from "./supabaseDatabase-
|
|
1
|
+
import { i as plugins, n as supabaseDatabase, t as SupabaseDatabaseConfig } from "./supabaseDatabase-C0-IshLi.mjs";
|
|
2
2
|
import { StoragePluginWith } from "@hot-updater/plugin-core";
|
|
3
3
|
//#region src/supabaseEdgeFunctionStorage.d.ts
|
|
4
4
|
interface SupabaseEdgeFunctionStorageConfig {
|
package/dist/iac/index.cjs
CHANGED
|
@@ -7085,35 +7085,12 @@ const assertSkippedConfigDoesNotUseLegacySupabaseKey = async (configWriteResult)
|
|
|
7085
7085
|
_hot_updater_cli_tools.p.log.message("Update it to use 'supabaseServiceRoleKey' with 'HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY', then run init again.");
|
|
7086
7086
|
process.exit(1);
|
|
7087
7087
|
};
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
function App() {
|
|
7092
|
-
return null; // Replace with your app root
|
|
7093
|
-
}
|
|
7094
|
-
|
|
7095
|
-
HotUpdater.init({
|
|
7096
|
-
baseURL: "%%source%%",
|
|
7097
|
-
requestHeaders: {
|
|
7098
|
-
"x-api-key": %%apiKey%%,
|
|
7099
|
-
},
|
|
7100
|
-
});
|
|
7101
|
-
|
|
7102
|
-
// Call HotUpdater.checkForUpdate({ updateStrategy: "appVersion" })
|
|
7103
|
-
// when your app is ready to check.
|
|
7104
|
-
export default App;`;
|
|
7105
|
-
const getSupabaseReactNativeSource = ({ apiKey, functionName, projectId }) => (0, _hot_updater_cli_tools.transformTemplate)(SOURCE_TEMPLATE, {
|
|
7106
|
-
apiKey: JSON.stringify(apiKey),
|
|
7107
|
-
source: `https://${projectId}.supabase.co/functions/v1/${functionName}`
|
|
7108
|
-
});
|
|
7088
|
+
/** The Edge Function URL an app sets as its baseURL. */
|
|
7089
|
+
const getSupabaseFunctionUrl = ({ functionName, projectId }) => `https://${projectId}.supabase.co/functions/v1/${functionName}`;
|
|
7109
7090
|
const reportSupabaseOriginCatalogReady = () => {
|
|
7110
7091
|
_hot_updater_cli_tools.p.log.success("Release catalog endpoint is ready in origin-only mode.");
|
|
7111
7092
|
_hot_updater_cli_tools.p.log.info("Catalog checks still invoke the Supabase Edge Function.");
|
|
7112
7093
|
};
|
|
7113
|
-
const reportSupabaseApiKey = (apiKey) => {
|
|
7114
|
-
_hot_updater_cli_tools.p.note(apiKey, "API Key");
|
|
7115
|
-
_hot_updater_cli_tools.p.log.message("Store this API key separately in a secure place.");
|
|
7116
|
-
};
|
|
7117
7094
|
const resolvePackageExportPath = async (packageName, exportName) => {
|
|
7118
7095
|
const packageJsonPath = require$1.resolve(`${packageName}/package.json`);
|
|
7119
7096
|
const exportTarget = JSON.parse(await fs_promises.default.readFile(packageJsonPath, "utf-8")).exports?.[exportName];
|
|
@@ -7656,13 +7633,13 @@ const runInitWithoutCliMetadata = async ({ build, envFile }) => {
|
|
|
7656
7633
|
supabaseServiceRoleKey: projectAccess.serviceRoleApiKey,
|
|
7657
7634
|
supabaseUrl: `https://${project.id}.supabase.co`
|
|
7658
7635
|
});
|
|
7659
|
-
let
|
|
7636
|
+
let credential;
|
|
7660
7637
|
try {
|
|
7661
|
-
|
|
7662
|
-
|
|
7638
|
+
credential = await (0, _hot_updater_server_db.provisionClientCredential)(databasePlugin, plugins, {
|
|
7639
|
+
env: initInputEnv,
|
|
7663
7640
|
name: "Supabase init"
|
|
7664
|
-
})
|
|
7665
|
-
await (0, _hot_updater_cli_tools.makeEnv)({
|
|
7641
|
+
});
|
|
7642
|
+
if (credential !== void 0) await (0, _hot_updater_cli_tools.makeEnv)({ [credential.env]: credential.value });
|
|
7666
7643
|
} finally {
|
|
7667
7644
|
await databasePlugin.dispose?.();
|
|
7668
7645
|
}
|
|
@@ -7675,12 +7652,14 @@ const runInitWithoutCliMetadata = async ({ build, envFile }) => {
|
|
|
7675
7652
|
else if (configWriteResult.status === "merged") _hot_updater_cli_tools.p.log.success("Updated 'hot-updater.config.ts' file with Supabase settings.");
|
|
7676
7653
|
else _hot_updater_cli_tools.p.log.warn(`Kept existing 'hot-updater.config.ts' unchanged: ${configWriteResult.reason}`);
|
|
7677
7654
|
await (0, _hot_updater_cli_tools.generateHotUpdaterPlugins)("@hot-updater/supabase");
|
|
7678
|
-
_hot_updater_cli_tools.
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7655
|
+
(0, _hot_updater_cli_tools.printAppSetup)({
|
|
7656
|
+
baseURL: getSupabaseFunctionUrl({
|
|
7657
|
+
functionName,
|
|
7658
|
+
projectId: project.id
|
|
7659
|
+
}),
|
|
7660
|
+
...credential === void 0 ? {} : { credential },
|
|
7661
|
+
clientPlugins: (0, _hot_updater_server_db.clientPluginsOf)(plugins)
|
|
7662
|
+
});
|
|
7684
7663
|
reportSupabaseOriginCatalogReady();
|
|
7685
7664
|
_hot_updater_cli_tools.p.log.message(`Next step: ${(0, _hot_updater_cli_tools.link)("https://hot-updater.dev/docs/managed/supabase#step-4-add-hotupdater-to-your-project")}`);
|
|
7686
7665
|
_hot_updater_cli_tools.p.log.success("Done! 🎉");
|
|
@@ -7689,9 +7668,8 @@ const runInit = (options) => withSupabaseCliMetadataCleanup(process.cwd(), () =>
|
|
|
7689
7668
|
//#endregion
|
|
7690
7669
|
exports.createSelectedBucket = createSelectedBucket;
|
|
7691
7670
|
exports.getLegacySupabaseConfigReference = getLegacySupabaseConfigReference;
|
|
7671
|
+
exports.getSupabaseFunctionUrl = getSupabaseFunctionUrl;
|
|
7692
7672
|
exports.getSupabaseProjectAccess = getSupabaseProjectAccess;
|
|
7693
|
-
exports.getSupabaseReactNativeSource = getSupabaseReactNativeSource;
|
|
7694
|
-
exports.reportSupabaseApiKey = reportSupabaseApiKey;
|
|
7695
7673
|
exports.reportSupabaseOriginCatalogReady = reportSupabaseOriginCatalogReady;
|
|
7696
7674
|
exports.resolveEdgeFunctionDenoConfig = resolveEdgeFunctionDenoConfig;
|
|
7697
7675
|
exports.runInit = runInit;
|
package/dist/iac/index.d.cts
CHANGED
|
@@ -45,13 +45,12 @@ interface SupabaseManagementApi {
|
|
|
45
45
|
//#endregion
|
|
46
46
|
//#region iac/index.d.ts
|
|
47
47
|
declare const getLegacySupabaseConfigReference: (configText: string) => "HOT_UPDATER_SUPABASE_ANON_KEY" | "supabaseAnonKey" | null;
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
/** The Edge Function URL an app sets as its baseURL. */
|
|
49
|
+
declare const getSupabaseFunctionUrl: ({ functionName, projectId }: {
|
|
50
50
|
readonly functionName: string;
|
|
51
51
|
readonly projectId: string;
|
|
52
52
|
}) => string;
|
|
53
53
|
declare const reportSupabaseOriginCatalogReady: () => void;
|
|
54
|
-
declare const reportSupabaseApiKey: (apiKey: string) => void;
|
|
55
54
|
declare const resolveEdgeFunctionDenoConfig: (targetDir: string) => Promise<{
|
|
56
55
|
imports: Record<string, string>;
|
|
57
56
|
}>;
|
|
@@ -99,4 +98,4 @@ declare const getSupabaseProjectAccess: ({ accessToken, managementApi, project,
|
|
|
99
98
|
declare const withSupabaseCliMetadataCleanup: <Result>(cwd: string, operation: () => Promise<Result>) => Promise<Result>;
|
|
100
99
|
declare const runInit: (options: RunInitOptions) => Promise<void>;
|
|
101
100
|
//#endregion
|
|
102
|
-
export { SupabaseBucketSelection, SupabaseProjectSelection, createSelectedBucket, getLegacySupabaseConfigReference,
|
|
101
|
+
export { SupabaseBucketSelection, SupabaseProjectSelection, createSelectedBucket, getLegacySupabaseConfigReference, getSupabaseFunctionUrl, getSupabaseProjectAccess, reportSupabaseOriginCatalogReady, resolveEdgeFunctionDenoConfig, runInit, selectBucket, selectProject, waitForSupabaseProjectReady, waitForSupabaseSchemaReady, withSupabaseCliMetadataCleanup };
|
package/dist/iac/index.d.mts
CHANGED
|
@@ -45,13 +45,12 @@ interface SupabaseManagementApi {
|
|
|
45
45
|
//#endregion
|
|
46
46
|
//#region iac/index.d.ts
|
|
47
47
|
declare const getLegacySupabaseConfigReference: (configText: string) => "HOT_UPDATER_SUPABASE_ANON_KEY" | "supabaseAnonKey" | null;
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
/** The Edge Function URL an app sets as its baseURL. */
|
|
49
|
+
declare const getSupabaseFunctionUrl: ({ functionName, projectId }: {
|
|
50
50
|
readonly functionName: string;
|
|
51
51
|
readonly projectId: string;
|
|
52
52
|
}) => string;
|
|
53
53
|
declare const reportSupabaseOriginCatalogReady: () => void;
|
|
54
|
-
declare const reportSupabaseApiKey: (apiKey: string) => void;
|
|
55
54
|
declare const resolveEdgeFunctionDenoConfig: (targetDir: string) => Promise<{
|
|
56
55
|
imports: Record<string, string>;
|
|
57
56
|
}>;
|
|
@@ -99,4 +98,4 @@ declare const getSupabaseProjectAccess: ({ accessToken, managementApi, project,
|
|
|
99
98
|
declare const withSupabaseCliMetadataCleanup: <Result>(cwd: string, operation: () => Promise<Result>) => Promise<Result>;
|
|
100
99
|
declare const runInit: (options: RunInitOptions) => Promise<void>;
|
|
101
100
|
//#endregion
|
|
102
|
-
export { SupabaseBucketSelection, SupabaseProjectSelection, createSelectedBucket, getLegacySupabaseConfigReference,
|
|
101
|
+
export { SupabaseBucketSelection, SupabaseProjectSelection, createSelectedBucket, getLegacySupabaseConfigReference, getSupabaseFunctionUrl, getSupabaseProjectAccess, reportSupabaseOriginCatalogReady, resolveEdgeFunctionDenoConfig, runInit, selectBucket, selectProject, waitForSupabaseProjectReady, waitForSupabaseSchemaReady, withSupabaseCliMetadataCleanup };
|
package/dist/iac/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import fs from "fs/promises";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import { ConfigBuilder, InitError, MissingInitInputsError, assertInfrastructureGenerationAtUrl, assertInitInputs, assertInitProviderInputs, confirmInitInputPersistence, copyDirToTmp, createHotUpdaterConfigScaffoldFromBuilder, generateHotUpdaterPlugins, getHotUpdaterEnvValue, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, link, makeEnv, p, readHotUpdaterInitEnv, resolveInitProviderInput, resolvePackageVersion, transformEnv, transformTemplate, writeHotUpdaterConfig } from "@hot-updater/cli-tools";
|
|
5
|
-
import {
|
|
4
|
+
import { ConfigBuilder, InitError, MissingInitInputsError, assertInfrastructureGenerationAtUrl, assertInitInputs, assertInitProviderInputs, confirmInitInputPersistence, copyDirToTmp, createHotUpdaterConfigScaffoldFromBuilder, generateHotUpdaterPlugins, getHotUpdaterEnvValue, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, link, makeEnv, p, printAppSetup, readHotUpdaterInitEnv, resolveInitProviderInput, resolvePackageVersion, transformEnv, transformTemplate, writeHotUpdaterConfig } from "@hot-updater/cli-tools";
|
|
5
|
+
import { clientPluginsOf, provisionClientCredential } from "@hot-updater/server/db";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { ChildProcess, execFile, spawn, spawnSync } from "node:child_process";
|
|
8
8
|
import { StringDecoder } from "node:string_decoder";
|
|
@@ -7080,35 +7080,12 @@ const assertSkippedConfigDoesNotUseLegacySupabaseKey = async (configWriteResult)
|
|
|
7080
7080
|
p.log.message("Update it to use 'supabaseServiceRoleKey' with 'HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY', then run init again.");
|
|
7081
7081
|
process.exit(1);
|
|
7082
7082
|
};
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
function App() {
|
|
7087
|
-
return null; // Replace with your app root
|
|
7088
|
-
}
|
|
7089
|
-
|
|
7090
|
-
HotUpdater.init({
|
|
7091
|
-
baseURL: "%%source%%",
|
|
7092
|
-
requestHeaders: {
|
|
7093
|
-
"x-api-key": %%apiKey%%,
|
|
7094
|
-
},
|
|
7095
|
-
});
|
|
7096
|
-
|
|
7097
|
-
// Call HotUpdater.checkForUpdate({ updateStrategy: "appVersion" })
|
|
7098
|
-
// when your app is ready to check.
|
|
7099
|
-
export default App;`;
|
|
7100
|
-
const getSupabaseReactNativeSource = ({ apiKey, functionName, projectId }) => transformTemplate(SOURCE_TEMPLATE, {
|
|
7101
|
-
apiKey: JSON.stringify(apiKey),
|
|
7102
|
-
source: `https://${projectId}.supabase.co/functions/v1/${functionName}`
|
|
7103
|
-
});
|
|
7083
|
+
/** The Edge Function URL an app sets as its baseURL. */
|
|
7084
|
+
const getSupabaseFunctionUrl = ({ functionName, projectId }) => `https://${projectId}.supabase.co/functions/v1/${functionName}`;
|
|
7104
7085
|
const reportSupabaseOriginCatalogReady = () => {
|
|
7105
7086
|
p.log.success("Release catalog endpoint is ready in origin-only mode.");
|
|
7106
7087
|
p.log.info("Catalog checks still invoke the Supabase Edge Function.");
|
|
7107
7088
|
};
|
|
7108
|
-
const reportSupabaseApiKey = (apiKey) => {
|
|
7109
|
-
p.note(apiKey, "API Key");
|
|
7110
|
-
p.log.message("Store this API key separately in a secure place.");
|
|
7111
|
-
};
|
|
7112
7089
|
const resolvePackageExportPath = async (packageName, exportName) => {
|
|
7113
7090
|
const packageJsonPath = require$1.resolve(`${packageName}/package.json`);
|
|
7114
7091
|
const exportTarget = JSON.parse(await fs.readFile(packageJsonPath, "utf-8")).exports?.[exportName];
|
|
@@ -7651,13 +7628,13 @@ const runInitWithoutCliMetadata = async ({ build, envFile }) => {
|
|
|
7651
7628
|
supabaseServiceRoleKey: projectAccess.serviceRoleApiKey,
|
|
7652
7629
|
supabaseUrl: `https://${project.id}.supabase.co`
|
|
7653
7630
|
});
|
|
7654
|
-
let
|
|
7631
|
+
let credential;
|
|
7655
7632
|
try {
|
|
7656
|
-
|
|
7657
|
-
|
|
7633
|
+
credential = await provisionClientCredential(databasePlugin, plugins, {
|
|
7634
|
+
env: initInputEnv,
|
|
7658
7635
|
name: "Supabase init"
|
|
7659
|
-
})
|
|
7660
|
-
await makeEnv({
|
|
7636
|
+
});
|
|
7637
|
+
if (credential !== void 0) await makeEnv({ [credential.env]: credential.value });
|
|
7661
7638
|
} finally {
|
|
7662
7639
|
await databasePlugin.dispose?.();
|
|
7663
7640
|
}
|
|
@@ -7670,16 +7647,18 @@ const runInitWithoutCliMetadata = async ({ build, envFile }) => {
|
|
|
7670
7647
|
else if (configWriteResult.status === "merged") p.log.success("Updated 'hot-updater.config.ts' file with Supabase settings.");
|
|
7671
7648
|
else p.log.warn(`Kept existing 'hot-updater.config.ts' unchanged: ${configWriteResult.reason}`);
|
|
7672
7649
|
await generateHotUpdaterPlugins("@hot-updater/supabase");
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7650
|
+
printAppSetup({
|
|
7651
|
+
baseURL: getSupabaseFunctionUrl({
|
|
7652
|
+
functionName,
|
|
7653
|
+
projectId: project.id
|
|
7654
|
+
}),
|
|
7655
|
+
...credential === void 0 ? {} : { credential },
|
|
7656
|
+
clientPlugins: clientPluginsOf(plugins)
|
|
7657
|
+
});
|
|
7679
7658
|
reportSupabaseOriginCatalogReady();
|
|
7680
7659
|
p.log.message(`Next step: ${link("https://hot-updater.dev/docs/managed/supabase#step-4-add-hotupdater-to-your-project")}`);
|
|
7681
7660
|
p.log.success("Done! 🎉");
|
|
7682
7661
|
};
|
|
7683
7662
|
const runInit = (options) => withSupabaseCliMetadataCleanup(process.cwd(), () => runInitWithoutCliMetadata(options));
|
|
7684
7663
|
//#endregion
|
|
7685
|
-
export { createSelectedBucket, getLegacySupabaseConfigReference,
|
|
7664
|
+
export { createSelectedBucket, getLegacySupabaseConfigReference, getSupabaseFunctionUrl, getSupabaseProjectAccess, reportSupabaseOriginCatalogReady, resolveEdgeFunctionDenoConfig, runInit, selectBucket, selectProject, waitForSupabaseProjectReady, waitForSupabaseSchemaReady, withSupabaseCliMetadataCleanup };
|
package/dist/index.cjs
CHANGED
|
@@ -4,7 +4,7 @@ let _hot_updater_server_database = require("@hot-updater/server/database");
|
|
|
4
4
|
let _hot_updater_server_db = require("@hot-updater/server/db");
|
|
5
5
|
//#region src/supabaseSchema.ts
|
|
6
6
|
/** Every table the apply RPC may name: models, index tables, settings, and the write guard. */
|
|
7
|
-
const supabaseTableNames = (schema = _hot_updater_server_database.
|
|
7
|
+
const supabaseTableNames = (schema = _hot_updater_server_database.coreTarget.schema) => [
|
|
8
8
|
...schema.tables.flatMap((table) => [table.name, ...table.indexes.filter((index) => (0, _hot_updater_server_database.isMultiIndex)(table, index)).map((index) => `${table.name}__${index.name}`)]),
|
|
9
9
|
_hot_updater_server_database.SETTINGS_TABLE.name,
|
|
10
10
|
_hot_updater_server_database.WRITE_GUARD_TABLE.name
|
|
@@ -102,9 +102,9 @@ $apply$`;
|
|
|
102
102
|
* write guard, row-level security on every table (no policy, so only the
|
|
103
103
|
* service role reads or writes), the apply RPC for the service role alone,
|
|
104
104
|
* and the settings rows last. Every statement can run again, so a migration
|
|
105
|
-
* for a server's plugin tables repeats
|
|
105
|
+
* for a server's plugin tables repeats core's.
|
|
106
106
|
*/
|
|
107
|
-
const supabaseSchemaStatements = ({ schema, settings: expected } = _hot_updater_server_database.
|
|
107
|
+
const supabaseSchemaStatements = ({ schema, settings: expected } = _hot_updater_server_database.coreTarget) => {
|
|
108
108
|
const engine = (0, _hot_updater_server_db.generateEngineSql)("postgresql", schema, expected, { tablePrefix: require_plugins.SUPABASE_TABLE_PREFIX });
|
|
109
109
|
const settings = engine.slice(-Object.keys(expected).length);
|
|
110
110
|
const tables = supabaseTableNames(schema);
|
|
@@ -124,14 +124,14 @@ const supabaseSchemaSql = (target) => `-- HotUpdater.schema\n\n${supabaseSchemaS
|
|
|
124
124
|
//#region src/supabaseMigration.ts
|
|
125
125
|
/**
|
|
126
126
|
* Hot Updater's database on Supabase, with the migration `hot-updater db
|
|
127
|
-
* generate` writes to `supabase/migrations`:
|
|
128
|
-
*
|
|
129
|
-
*
|
|
127
|
+
* generate` writes to `supabase/migrations`: core's tables, the server's
|
|
128
|
+
* plugin tables, the apply RPC that may name them, and their settings rows.
|
|
129
|
+
* `supabase db push` applies it.
|
|
130
130
|
*/
|
|
131
131
|
const supabaseDatabase = (config) => ({
|
|
132
132
|
...require_plugins.supabaseDatabase(config),
|
|
133
|
-
generateSchema: ((version, _name, target = _hot_updater_server_database.
|
|
134
|
-
if (version !== "latest" && version !== _hot_updater_server_database.
|
|
133
|
+
generateSchema: ((version, _name, target = _hot_updater_server_database.coreTarget) => {
|
|
134
|
+
if (version !== "latest" && version !== _hot_updater_server_database.coreSettings["schema.core"]) throw new Error(`Invalid version ${version}`);
|
|
135
135
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14);
|
|
136
136
|
return {
|
|
137
137
|
code: supabaseSchemaSql(target),
|
package/dist/index.d.cts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { i as plugins, r as SupabaseServiceRoleConfig, t as SupabaseDatabaseConfig } from "./supabaseDatabase-
|
|
1
|
+
import { i as plugins, r as SupabaseServiceRoleConfig, t as SupabaseDatabaseConfig } from "./supabaseDatabase-C0-IshLi.cjs";
|
|
2
2
|
import { StoragePluginWith } from "@hot-updater/plugin-core";
|
|
3
3
|
import { ToolingDatabase } from "@hot-updater/server/db";
|
|
4
4
|
//#region src/supabaseMigration.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Hot Updater's database on Supabase, with the migration `hot-updater db
|
|
7
|
-
* generate` writes to `supabase/migrations`:
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* generate` writes to `supabase/migrations`: core's tables, the server's
|
|
8
|
+
* plugin tables, the apply RPC that may name them, and their settings rows.
|
|
9
|
+
* `supabase db push` applies it.
|
|
10
10
|
*/
|
|
11
11
|
declare const supabaseDatabase: (config: SupabaseDatabaseConfig) => ToolingDatabase;
|
|
12
12
|
//#endregion
|
package/dist/index.d.mts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { i as plugins, r as SupabaseServiceRoleConfig, t as SupabaseDatabaseConfig } from "./supabaseDatabase-
|
|
1
|
+
import { i as plugins, r as SupabaseServiceRoleConfig, t as SupabaseDatabaseConfig } from "./supabaseDatabase-C0-IshLi.mjs";
|
|
2
2
|
import { ToolingDatabase } from "@hot-updater/server/db";
|
|
3
3
|
import { StoragePluginWith } from "@hot-updater/plugin-core";
|
|
4
4
|
//#region src/supabaseMigration.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Hot Updater's database on Supabase, with the migration `hot-updater db
|
|
7
|
-
* generate` writes to `supabase/migrations`:
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* generate` writes to `supabase/migrations`: core's tables, the server's
|
|
8
|
+
* plugin tables, the apply RPC that may name them, and their settings rows.
|
|
9
|
+
* `supabase db push` applies it.
|
|
10
10
|
*/
|
|
11
11
|
declare const supabaseDatabase: (config: SupabaseDatabaseConfig) => ToolingDatabase;
|
|
12
12
|
//#endregion
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { a as SUPABASE_TABLE_PREFIX, i as SUPABASE_APPLY_FUNCTION, n as supabaseStorage, r as supabaseDatabase$1, t as plugins } from "./plugins--10vxSpf.mjs";
|
|
2
|
-
import { SETTINGS_TABLE, WRITE_GUARD_TABLE,
|
|
2
|
+
import { SETTINGS_TABLE, WRITE_GUARD_TABLE, coreSettings, coreTarget, createTableStatements, isMultiIndex } from "@hot-updater/server/database";
|
|
3
3
|
import { generateEngineSql } from "@hot-updater/server/db";
|
|
4
4
|
//#region src/supabaseSchema.ts
|
|
5
5
|
/** Every table the apply RPC may name: models, index tables, settings, and the write guard. */
|
|
6
|
-
const supabaseTableNames = (schema =
|
|
6
|
+
const supabaseTableNames = (schema = coreTarget.schema) => [
|
|
7
7
|
...schema.tables.flatMap((table) => [table.name, ...table.indexes.filter((index) => isMultiIndex(table, index)).map((index) => `${table.name}__${index.name}`)]),
|
|
8
8
|
SETTINGS_TABLE.name,
|
|
9
9
|
WRITE_GUARD_TABLE.name
|
|
@@ -101,9 +101,9 @@ $apply$`;
|
|
|
101
101
|
* write guard, row-level security on every table (no policy, so only the
|
|
102
102
|
* service role reads or writes), the apply RPC for the service role alone,
|
|
103
103
|
* and the settings rows last. Every statement can run again, so a migration
|
|
104
|
-
* for a server's plugin tables repeats
|
|
104
|
+
* for a server's plugin tables repeats core's.
|
|
105
105
|
*/
|
|
106
|
-
const supabaseSchemaStatements = ({ schema, settings: expected } =
|
|
106
|
+
const supabaseSchemaStatements = ({ schema, settings: expected } = coreTarget) => {
|
|
107
107
|
const engine = generateEngineSql("postgresql", schema, expected, { tablePrefix: SUPABASE_TABLE_PREFIX });
|
|
108
108
|
const settings = engine.slice(-Object.keys(expected).length);
|
|
109
109
|
const tables = supabaseTableNames(schema);
|
|
@@ -123,14 +123,14 @@ const supabaseSchemaSql = (target) => `-- HotUpdater.schema\n\n${supabaseSchemaS
|
|
|
123
123
|
//#region src/supabaseMigration.ts
|
|
124
124
|
/**
|
|
125
125
|
* Hot Updater's database on Supabase, with the migration `hot-updater db
|
|
126
|
-
* generate` writes to `supabase/migrations`:
|
|
127
|
-
*
|
|
128
|
-
*
|
|
126
|
+
* generate` writes to `supabase/migrations`: core's tables, the server's
|
|
127
|
+
* plugin tables, the apply RPC that may name them, and their settings rows.
|
|
128
|
+
* `supabase db push` applies it.
|
|
129
129
|
*/
|
|
130
130
|
const supabaseDatabase = (config) => ({
|
|
131
131
|
...supabaseDatabase$1(config),
|
|
132
|
-
generateSchema: ((version, _name, target =
|
|
133
|
-
if (version !== "latest" && version !==
|
|
132
|
+
generateSchema: ((version, _name, target = coreTarget) => {
|
|
133
|
+
if (version !== "latest" && version !== coreSettings["schema.core"]) throw new Error(`Invalid version ${version}`);
|
|
134
134
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14);
|
|
135
135
|
return {
|
|
136
136
|
code: supabaseSchemaSql(target),
|
|
@@ -133,12 +133,7 @@ declare const plugins: readonly [import("@hot-updater/server/plugins").HotUpdate
|
|
|
133
133
|
readonly received_at_ms: {
|
|
134
134
|
readonly type: "integer";
|
|
135
135
|
};
|
|
136
|
-
|
|
137
|
-
readonly type: "string";
|
|
138
|
-
readonly maxLength: 36;
|
|
139
|
-
readonly required: false;
|
|
140
|
-
};
|
|
141
|
-
}, {}, {
|
|
136
|
+
}, Readonly<Record<string, import("@hot-updater/server/database").FieldType>>, {
|
|
142
137
|
readonly byUser: {
|
|
143
138
|
readonly eq: readonly ["user_id"];
|
|
144
139
|
readonly sort: readonly ["install_id"];
|
|
@@ -152,7 +147,7 @@ declare const plugins: readonly [import("@hot-updater/server/plugins").HotUpdate
|
|
|
152
147
|
readonly bucket_start_ms: {
|
|
153
148
|
readonly type: "integer";
|
|
154
149
|
};
|
|
155
|
-
}, "downloads" | "launches" | "failed_launches", {
|
|
150
|
+
}, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
|
|
156
151
|
readonly window: {
|
|
157
152
|
readonly eq: readonly ["identity"];
|
|
158
153
|
readonly sort: readonly ["bucket_start_ms"];
|
|
@@ -171,7 +166,63 @@ declare const plugins: readonly [import("@hot-updater/server/plugins").HotUpdate
|
|
|
171
166
|
readonly eq: readonly ["identity"];
|
|
172
167
|
readonly sort: readonly ["bucket_start_ms"];
|
|
173
168
|
};
|
|
174
|
-
}, "launch_users" | "activity_users", readonly ["identity", "bucket_start_ms"]>;
|
|
169
|
+
}, "launch_users" | "activity_users" | "failed_users", readonly ["identity", "bucket_start_ms"]>;
|
|
170
|
+
readonly insights_overview_daily: import("@hot-updater/server/database").AggregateDefinition<{
|
|
171
|
+
readonly identity: {
|
|
172
|
+
readonly type: "string";
|
|
173
|
+
readonly maxLength: 32;
|
|
174
|
+
};
|
|
175
|
+
readonly bucket_start_ms: {
|
|
176
|
+
readonly type: "integer";
|
|
177
|
+
};
|
|
178
|
+
}, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
|
|
179
|
+
readonly window: {
|
|
180
|
+
readonly eq: readonly ["identity"];
|
|
181
|
+
readonly sort: readonly ["bucket_start_ms"];
|
|
182
|
+
};
|
|
183
|
+
}, never, readonly ["identity", "bucket_start_ms"]>;
|
|
184
|
+
readonly insights_sketches_daily: import("@hot-updater/server/database").AggregateDefinition<{
|
|
185
|
+
readonly identity: {
|
|
186
|
+
readonly type: "string";
|
|
187
|
+
readonly maxLength: 32;
|
|
188
|
+
};
|
|
189
|
+
readonly bucket_start_ms: {
|
|
190
|
+
readonly type: "integer";
|
|
191
|
+
};
|
|
192
|
+
}, never, {
|
|
193
|
+
readonly window: {
|
|
194
|
+
readonly eq: readonly ["identity"];
|
|
195
|
+
readonly sort: readonly ["bucket_start_ms"];
|
|
196
|
+
};
|
|
197
|
+
}, "launch_users" | "activity_users" | "failed_users", readonly ["identity", "bucket_start_ms"]>;
|
|
198
|
+
readonly insights_overview_lifetime: import("@hot-updater/server/database").AggregateDefinition<{
|
|
199
|
+
readonly identity: {
|
|
200
|
+
readonly type: "string";
|
|
201
|
+
readonly maxLength: 32;
|
|
202
|
+
};
|
|
203
|
+
readonly bucket_start_ms: {
|
|
204
|
+
readonly type: "integer";
|
|
205
|
+
};
|
|
206
|
+
}, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
|
|
207
|
+
readonly window: {
|
|
208
|
+
readonly eq: readonly ["identity"];
|
|
209
|
+
readonly sort: readonly ["bucket_start_ms"];
|
|
210
|
+
};
|
|
211
|
+
}, never, readonly ["identity", "bucket_start_ms"]>;
|
|
212
|
+
readonly insights_sketches_lifetime: import("@hot-updater/server/database").AggregateDefinition<{
|
|
213
|
+
readonly identity: {
|
|
214
|
+
readonly type: "string";
|
|
215
|
+
readonly maxLength: 32;
|
|
216
|
+
};
|
|
217
|
+
readonly bucket_start_ms: {
|
|
218
|
+
readonly type: "integer";
|
|
219
|
+
};
|
|
220
|
+
}, never, {
|
|
221
|
+
readonly window: {
|
|
222
|
+
readonly eq: readonly ["identity"];
|
|
223
|
+
readonly sort: readonly ["bucket_start_ms"];
|
|
224
|
+
};
|
|
225
|
+
}, "failed_users", readonly ["identity", "bucket_start_ms"]>;
|
|
175
226
|
readonly insights_distribution: import("@hot-updater/server/database").AggregateDefinition<{
|
|
176
227
|
readonly channel: {
|
|
177
228
|
readonly type: "string";
|
|
@@ -254,22 +305,52 @@ declare const plugins: readonly [import("@hot-updater/server/plugins").HotUpdate
|
|
|
254
305
|
readonly sort: readonly ["bucket_start_ms"];
|
|
255
306
|
};
|
|
256
307
|
}, never, readonly ["platform", "channel", "type", "bundle_ref", "bucket_start_ms"]>;
|
|
308
|
+
readonly insights_failures: import("@hot-updater/server/database").AggregateDefinition<{
|
|
309
|
+
readonly platform: {
|
|
310
|
+
readonly type: "string";
|
|
311
|
+
readonly maxLength: 16;
|
|
312
|
+
};
|
|
313
|
+
readonly channel: {
|
|
314
|
+
readonly type: "string";
|
|
315
|
+
};
|
|
316
|
+
readonly bucket_start_ms: {
|
|
317
|
+
readonly type: "integer";
|
|
318
|
+
};
|
|
319
|
+
readonly release_id: {
|
|
320
|
+
readonly type: "string";
|
|
321
|
+
readonly maxLength: 36;
|
|
322
|
+
};
|
|
323
|
+
readonly stage: {
|
|
324
|
+
readonly type: "string";
|
|
325
|
+
readonly maxLength: 16;
|
|
326
|
+
};
|
|
327
|
+
readonly reason: {
|
|
328
|
+
readonly type: "string";
|
|
329
|
+
readonly maxLength: 64;
|
|
330
|
+
};
|
|
331
|
+
readonly detail: {
|
|
332
|
+
readonly type: "string";
|
|
333
|
+
readonly maxLength: 160;
|
|
334
|
+
};
|
|
335
|
+
}, "events", {
|
|
336
|
+
readonly byScope: {
|
|
337
|
+
readonly eq: readonly ["platform", "channel"];
|
|
338
|
+
readonly sort: readonly ["bucket_start_ms"];
|
|
339
|
+
};
|
|
340
|
+
}, never, readonly ["platform", "channel", "bucket_start_ms", "release_id", "stage", "reason", "detail"]>;
|
|
257
341
|
}, {
|
|
258
342
|
api: {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
343
|
+
retention: import("@hot-updater/server/plugins/insights").InsightsRetention;
|
|
344
|
+
recordEvent: (event: import("@hot-updater/server/plugins/insights").BundleEventRow) => Promise<void>;
|
|
345
|
+
listEvents: (input: import("@hot-updater/server/plugins/insights").InsightsListEventsInput) => Promise<readonly import("@hot-updater/server/plugins/insights").BundleEventRow[]>;
|
|
346
|
+
findLatestEvents: (input: import("@hot-updater/server/plugins/insights").InsightsFindLatestEventsInput) => Promise<readonly import("@hot-updater/server/plugins/insights").BundleEventRow[]>;
|
|
347
|
+
countLatestEvents: (input: import("@hot-updater/server/plugins/insights").InsightsCountLatestEventsInput) => Promise<number>;
|
|
348
|
+
countEvents: (input: import("@hot-updater/server/plugins/insights").InsightsCountEventsInput) => Promise<number>;
|
|
349
|
+
getReleaseActivity: (input: import("@hot-updater/server/plugins/insights").InsightsGetReleaseActivityInput) => Promise<import("@hot-updater/server/plugins/insights").InsightsGetReleaseActivityResult>;
|
|
350
|
+
getAppUsage: (input: import("@hot-updater/server/plugins/insights").InsightsGetAppUsageInput) => Promise<import("@hot-updater/server/plugins/insights").InsightsGetAppUsageResult>;
|
|
351
|
+
getUpdateFailures: (input: import("@hot-updater/server/plugins/insights").InsightsUpdateFailuresInput) => Promise<import("@hot-updater/server/plugins/insights").InsightsUpdateFailures>;
|
|
266
352
|
};
|
|
267
|
-
endpoints:
|
|
268
|
-
access: "client" | "admin";
|
|
269
|
-
method: "POST" | "GET";
|
|
270
|
-
path: "/events" | "/overview" | "/installations" | "/installations/:installId/events" | "/installations/:installId";
|
|
271
|
-
handler: (request: Request, params: Readonly<Record<string, string>>) => Promise<Response>;
|
|
272
|
-
}[];
|
|
353
|
+
endpoints: import("@hot-updater/server/plugins").PluginEndpoint[];
|
|
273
354
|
}, {}>, import("@hot-updater/server/plugins").HotUpdaterPlugin<"apiKeys", {
|
|
274
355
|
readonly api_keys: import("@hot-updater/server/database").TableDefinition<{
|
|
275
356
|
readonly id: {
|
|
@@ -312,21 +393,21 @@ declare const plugins: readonly [import("@hot-updater/server/plugins").HotUpdate
|
|
|
312
393
|
readonly apiKey: string;
|
|
313
394
|
readonly name: string;
|
|
314
395
|
readonly createdAtMs?: number;
|
|
315
|
-
}) => Promise<import("@hot-updater/server").CreatedApiKey>;
|
|
396
|
+
}) => Promise<import("@hot-updater/server/plugins/api-keys").CreatedApiKey>;
|
|
316
397
|
provision: (input: {
|
|
317
398
|
readonly existingApiKey?: string;
|
|
318
399
|
readonly name: string;
|
|
319
|
-
}) => Promise<import("@hot-updater/server").CreatedApiKey>;
|
|
400
|
+
}) => Promise<import("@hot-updater/server/plugins/api-keys").CreatedApiKey>;
|
|
320
401
|
create(input: {
|
|
321
402
|
readonly name: string;
|
|
322
403
|
}): Promise<{
|
|
323
404
|
readonly apiKey: string;
|
|
324
|
-
readonly record: import("@hot-updater/server").ApiKeyMetadata;
|
|
405
|
+
readonly record: import("@hot-updater/server/plugins/api-keys").ApiKeyMetadata;
|
|
325
406
|
}>;
|
|
326
|
-
list(): Promise<readonly import("@hot-updater/server").ApiKeyMetadata[]>;
|
|
407
|
+
list(): Promise<readonly import("@hot-updater/server/plugins/api-keys").ApiKeyMetadata[]>;
|
|
327
408
|
revoke(input: {
|
|
328
409
|
readonly id: string;
|
|
329
|
-
}): Promise<import("@hot-updater/server").ApiKeyMetadata | null>;
|
|
410
|
+
}): Promise<import("@hot-updater/server/plugins/api-keys").ApiKeyMetadata | null>;
|
|
330
411
|
};
|
|
331
412
|
clientAuth: {
|
|
332
413
|
varyHeaders: string[];
|
|
@@ -133,12 +133,7 @@ declare const plugins: readonly [import("@hot-updater/server/plugins").HotUpdate
|
|
|
133
133
|
readonly received_at_ms: {
|
|
134
134
|
readonly type: "integer";
|
|
135
135
|
};
|
|
136
|
-
|
|
137
|
-
readonly type: "string";
|
|
138
|
-
readonly maxLength: 36;
|
|
139
|
-
readonly required: false;
|
|
140
|
-
};
|
|
141
|
-
}, {}, {
|
|
136
|
+
}, Readonly<Record<string, import("@hot-updater/server/database").FieldType>>, {
|
|
142
137
|
readonly byUser: {
|
|
143
138
|
readonly eq: readonly ["user_id"];
|
|
144
139
|
readonly sort: readonly ["install_id"];
|
|
@@ -152,7 +147,7 @@ declare const plugins: readonly [import("@hot-updater/server/plugins").HotUpdate
|
|
|
152
147
|
readonly bucket_start_ms: {
|
|
153
148
|
readonly type: "integer";
|
|
154
149
|
};
|
|
155
|
-
}, "downloads" | "launches" | "failed_launches", {
|
|
150
|
+
}, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
|
|
156
151
|
readonly window: {
|
|
157
152
|
readonly eq: readonly ["identity"];
|
|
158
153
|
readonly sort: readonly ["bucket_start_ms"];
|
|
@@ -171,7 +166,63 @@ declare const plugins: readonly [import("@hot-updater/server/plugins").HotUpdate
|
|
|
171
166
|
readonly eq: readonly ["identity"];
|
|
172
167
|
readonly sort: readonly ["bucket_start_ms"];
|
|
173
168
|
};
|
|
174
|
-
}, "launch_users" | "activity_users", readonly ["identity", "bucket_start_ms"]>;
|
|
169
|
+
}, "launch_users" | "activity_users" | "failed_users", readonly ["identity", "bucket_start_ms"]>;
|
|
170
|
+
readonly insights_overview_daily: import("@hot-updater/server/database").AggregateDefinition<{
|
|
171
|
+
readonly identity: {
|
|
172
|
+
readonly type: "string";
|
|
173
|
+
readonly maxLength: 32;
|
|
174
|
+
};
|
|
175
|
+
readonly bucket_start_ms: {
|
|
176
|
+
readonly type: "integer";
|
|
177
|
+
};
|
|
178
|
+
}, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
|
|
179
|
+
readonly window: {
|
|
180
|
+
readonly eq: readonly ["identity"];
|
|
181
|
+
readonly sort: readonly ["bucket_start_ms"];
|
|
182
|
+
};
|
|
183
|
+
}, never, readonly ["identity", "bucket_start_ms"]>;
|
|
184
|
+
readonly insights_sketches_daily: import("@hot-updater/server/database").AggregateDefinition<{
|
|
185
|
+
readonly identity: {
|
|
186
|
+
readonly type: "string";
|
|
187
|
+
readonly maxLength: 32;
|
|
188
|
+
};
|
|
189
|
+
readonly bucket_start_ms: {
|
|
190
|
+
readonly type: "integer";
|
|
191
|
+
};
|
|
192
|
+
}, never, {
|
|
193
|
+
readonly window: {
|
|
194
|
+
readonly eq: readonly ["identity"];
|
|
195
|
+
readonly sort: readonly ["bucket_start_ms"];
|
|
196
|
+
};
|
|
197
|
+
}, "launch_users" | "activity_users" | "failed_users", readonly ["identity", "bucket_start_ms"]>;
|
|
198
|
+
readonly insights_overview_lifetime: import("@hot-updater/server/database").AggregateDefinition<{
|
|
199
|
+
readonly identity: {
|
|
200
|
+
readonly type: "string";
|
|
201
|
+
readonly maxLength: 32;
|
|
202
|
+
};
|
|
203
|
+
readonly bucket_start_ms: {
|
|
204
|
+
readonly type: "integer";
|
|
205
|
+
};
|
|
206
|
+
}, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
|
|
207
|
+
readonly window: {
|
|
208
|
+
readonly eq: readonly ["identity"];
|
|
209
|
+
readonly sort: readonly ["bucket_start_ms"];
|
|
210
|
+
};
|
|
211
|
+
}, never, readonly ["identity", "bucket_start_ms"]>;
|
|
212
|
+
readonly insights_sketches_lifetime: import("@hot-updater/server/database").AggregateDefinition<{
|
|
213
|
+
readonly identity: {
|
|
214
|
+
readonly type: "string";
|
|
215
|
+
readonly maxLength: 32;
|
|
216
|
+
};
|
|
217
|
+
readonly bucket_start_ms: {
|
|
218
|
+
readonly type: "integer";
|
|
219
|
+
};
|
|
220
|
+
}, never, {
|
|
221
|
+
readonly window: {
|
|
222
|
+
readonly eq: readonly ["identity"];
|
|
223
|
+
readonly sort: readonly ["bucket_start_ms"];
|
|
224
|
+
};
|
|
225
|
+
}, "failed_users", readonly ["identity", "bucket_start_ms"]>;
|
|
175
226
|
readonly insights_distribution: import("@hot-updater/server/database").AggregateDefinition<{
|
|
176
227
|
readonly channel: {
|
|
177
228
|
readonly type: "string";
|
|
@@ -254,22 +305,52 @@ declare const plugins: readonly [import("@hot-updater/server/plugins").HotUpdate
|
|
|
254
305
|
readonly sort: readonly ["bucket_start_ms"];
|
|
255
306
|
};
|
|
256
307
|
}, never, readonly ["platform", "channel", "type", "bundle_ref", "bucket_start_ms"]>;
|
|
308
|
+
readonly insights_failures: import("@hot-updater/server/database").AggregateDefinition<{
|
|
309
|
+
readonly platform: {
|
|
310
|
+
readonly type: "string";
|
|
311
|
+
readonly maxLength: 16;
|
|
312
|
+
};
|
|
313
|
+
readonly channel: {
|
|
314
|
+
readonly type: "string";
|
|
315
|
+
};
|
|
316
|
+
readonly bucket_start_ms: {
|
|
317
|
+
readonly type: "integer";
|
|
318
|
+
};
|
|
319
|
+
readonly release_id: {
|
|
320
|
+
readonly type: "string";
|
|
321
|
+
readonly maxLength: 36;
|
|
322
|
+
};
|
|
323
|
+
readonly stage: {
|
|
324
|
+
readonly type: "string";
|
|
325
|
+
readonly maxLength: 16;
|
|
326
|
+
};
|
|
327
|
+
readonly reason: {
|
|
328
|
+
readonly type: "string";
|
|
329
|
+
readonly maxLength: 64;
|
|
330
|
+
};
|
|
331
|
+
readonly detail: {
|
|
332
|
+
readonly type: "string";
|
|
333
|
+
readonly maxLength: 160;
|
|
334
|
+
};
|
|
335
|
+
}, "events", {
|
|
336
|
+
readonly byScope: {
|
|
337
|
+
readonly eq: readonly ["platform", "channel"];
|
|
338
|
+
readonly sort: readonly ["bucket_start_ms"];
|
|
339
|
+
};
|
|
340
|
+
}, never, readonly ["platform", "channel", "bucket_start_ms", "release_id", "stage", "reason", "detail"]>;
|
|
257
341
|
}, {
|
|
258
342
|
api: {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
343
|
+
retention: import("@hot-updater/server/plugins/insights").InsightsRetention;
|
|
344
|
+
recordEvent: (event: import("@hot-updater/server/plugins/insights").BundleEventRow) => Promise<void>;
|
|
345
|
+
listEvents: (input: import("@hot-updater/server/plugins/insights").InsightsListEventsInput) => Promise<readonly import("@hot-updater/server/plugins/insights").BundleEventRow[]>;
|
|
346
|
+
findLatestEvents: (input: import("@hot-updater/server/plugins/insights").InsightsFindLatestEventsInput) => Promise<readonly import("@hot-updater/server/plugins/insights").BundleEventRow[]>;
|
|
347
|
+
countLatestEvents: (input: import("@hot-updater/server/plugins/insights").InsightsCountLatestEventsInput) => Promise<number>;
|
|
348
|
+
countEvents: (input: import("@hot-updater/server/plugins/insights").InsightsCountEventsInput) => Promise<number>;
|
|
349
|
+
getReleaseActivity: (input: import("@hot-updater/server/plugins/insights").InsightsGetReleaseActivityInput) => Promise<import("@hot-updater/server/plugins/insights").InsightsGetReleaseActivityResult>;
|
|
350
|
+
getAppUsage: (input: import("@hot-updater/server/plugins/insights").InsightsGetAppUsageInput) => Promise<import("@hot-updater/server/plugins/insights").InsightsGetAppUsageResult>;
|
|
351
|
+
getUpdateFailures: (input: import("@hot-updater/server/plugins/insights").InsightsUpdateFailuresInput) => Promise<import("@hot-updater/server/plugins/insights").InsightsUpdateFailures>;
|
|
266
352
|
};
|
|
267
|
-
endpoints:
|
|
268
|
-
access: "client" | "admin";
|
|
269
|
-
method: "POST" | "GET";
|
|
270
|
-
path: "/events" | "/overview" | "/installations" | "/installations/:installId/events" | "/installations/:installId";
|
|
271
|
-
handler: (request: Request, params: Readonly<Record<string, string>>) => Promise<Response>;
|
|
272
|
-
}[];
|
|
353
|
+
endpoints: import("@hot-updater/server/plugins").PluginEndpoint[];
|
|
273
354
|
}, {}>, import("@hot-updater/server/plugins").HotUpdaterPlugin<"apiKeys", {
|
|
274
355
|
readonly api_keys: import("@hot-updater/server/database").TableDefinition<{
|
|
275
356
|
readonly id: {
|
|
@@ -312,21 +393,21 @@ declare const plugins: readonly [import("@hot-updater/server/plugins").HotUpdate
|
|
|
312
393
|
readonly apiKey: string;
|
|
313
394
|
readonly name: string;
|
|
314
395
|
readonly createdAtMs?: number;
|
|
315
|
-
}) => Promise<import("@hot-updater/server").CreatedApiKey>;
|
|
396
|
+
}) => Promise<import("@hot-updater/server/plugins/api-keys").CreatedApiKey>;
|
|
316
397
|
provision: (input: {
|
|
317
398
|
readonly existingApiKey?: string;
|
|
318
399
|
readonly name: string;
|
|
319
|
-
}) => Promise<import("@hot-updater/server").CreatedApiKey>;
|
|
400
|
+
}) => Promise<import("@hot-updater/server/plugins/api-keys").CreatedApiKey>;
|
|
320
401
|
create(input: {
|
|
321
402
|
readonly name: string;
|
|
322
403
|
}): Promise<{
|
|
323
404
|
readonly apiKey: string;
|
|
324
|
-
readonly record: import("@hot-updater/server").ApiKeyMetadata;
|
|
405
|
+
readonly record: import("@hot-updater/server/plugins/api-keys").ApiKeyMetadata;
|
|
325
406
|
}>;
|
|
326
|
-
list(): Promise<readonly import("@hot-updater/server").ApiKeyMetadata[]>;
|
|
407
|
+
list(): Promise<readonly import("@hot-updater/server/plugins/api-keys").ApiKeyMetadata[]>;
|
|
327
408
|
revoke(input: {
|
|
328
409
|
readonly id: string;
|
|
329
|
-
}): Promise<import("@hot-updater/server").ApiKeyMetadata | null>;
|
|
410
|
+
}): Promise<import("@hot-updater/server/plugins/api-keys").ApiKeyMetadata | null>;
|
|
330
411
|
};
|
|
331
412
|
clientAuth: {
|
|
332
413
|
varyHeaders: string[];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/supabase",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-rc.
|
|
4
|
+
"version": "1.0.0-rc.18",
|
|
5
5
|
"description": "React Native OTA solution for self-hosted",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
"hono": "4.12.34",
|
|
57
57
|
"uuidv7": "^1.0.2",
|
|
58
58
|
"@hot-updater/core": "1.0.0-rc.15",
|
|
59
|
-
"@hot-updater/
|
|
60
|
-
"@hot-updater/server": "1.0.0-rc.
|
|
61
|
-
"@hot-updater/
|
|
59
|
+
"@hot-updater/plugin-core": "1.0.0-rc.17",
|
|
60
|
+
"@hot-updater/server": "1.0.0-rc.18",
|
|
61
|
+
"@hot-updater/cli-tools": "1.0.0-rc.17"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@electric-sql/pglite": "0.4.1",
|
|
@@ -68,9 +68,9 @@
|
|
|
68
68
|
"execa": "9.5.2",
|
|
69
69
|
"@types/node": "^20",
|
|
70
70
|
"mime": "^4.0.4",
|
|
71
|
-
"@hot-updater/
|
|
72
|
-
"@hot-updater/
|
|
73
|
-
"@hot-updater/
|
|
71
|
+
"@hot-updater/mock": "1.0.0-rc.17",
|
|
72
|
+
"@hot-updater/postgres": "1.0.0-rc.18",
|
|
73
|
+
"@hot-updater/test-utils": "1.0.0-rc.17"
|
|
74
74
|
},
|
|
75
75
|
"scripts": {
|
|
76
76
|
"build": "tsdown",
|
|
@@ -15,17 +15,13 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
15
15
|
|
|
16
16
|
import { resolvePackageVersion, transformEnv } from "@hot-updater/cli-tools";
|
|
17
17
|
import type { Bundle } from "@hot-updater/core";
|
|
18
|
-
import {
|
|
19
|
-
type BundleEventRow,
|
|
20
|
-
type HotUpdaterCoreApi,
|
|
21
|
-
rowToBundle,
|
|
22
|
-
} from "@hot-updater/plugin-core";
|
|
18
|
+
import { type HotUpdaterCoreApi, rowToBundle } from "@hot-updater/plugin-core";
|
|
23
19
|
import { createHotUpdater } from "@hot-updater/server";
|
|
24
20
|
import {
|
|
25
|
-
builtInSchema,
|
|
26
21
|
createDatabaseEngine,
|
|
27
22
|
createSqlAdapter,
|
|
28
23
|
type RetryOptions,
|
|
24
|
+
toolingTargetOf,
|
|
29
25
|
} from "@hot-updater/server/database";
|
|
30
26
|
import {
|
|
31
27
|
createDatabaseCoreApi,
|
|
@@ -37,7 +33,9 @@ import {
|
|
|
37
33
|
createInsightsModel,
|
|
38
34
|
insights,
|
|
39
35
|
insightsSchema,
|
|
36
|
+
type BundleEventRow,
|
|
40
37
|
} from "@hot-updater/server/plugins/insights";
|
|
38
|
+
import { insightsTestSuite } from "@hot-updater/server/plugins/insights/testing";
|
|
41
39
|
import {
|
|
42
40
|
runContentionHarness,
|
|
43
41
|
setupDatabaseTestSuite,
|
|
@@ -55,6 +53,7 @@ import {
|
|
|
55
53
|
stopRuntime,
|
|
56
54
|
waitForHttpOk,
|
|
57
55
|
} from "../../../../packages/test-utils/src/runtimeProcess";
|
|
56
|
+
import { plugins } from "../../src/plugins";
|
|
58
57
|
import { supabaseDatabase } from "../../src/supabaseDatabase";
|
|
59
58
|
import { supabaseExecutor } from "../../src/supabaseExecutor";
|
|
60
59
|
import {
|
|
@@ -174,7 +173,6 @@ const rolloutMove = (
|
|
|
174
173
|
app_version: "1.0.0",
|
|
175
174
|
channel: "production",
|
|
176
175
|
metadata: {
|
|
177
|
-
username: null,
|
|
178
176
|
cohort: "1",
|
|
179
177
|
update_strategy: "appVersion",
|
|
180
178
|
fingerprint_hash: null,
|
|
@@ -251,9 +249,9 @@ describe.sequential("supabase edge runtime acceptance", () => {
|
|
|
251
249
|
});
|
|
252
250
|
};
|
|
253
251
|
|
|
254
|
-
/** Empties every data table but `keep
|
|
252
|
+
/** Empties every data table but `keep`, the managed plugins' included; the settings rows stay. */
|
|
255
253
|
const truncateDataTables = (keep: readonly string[] = []) => {
|
|
256
|
-
const tables = supabaseTableNames()
|
|
254
|
+
const tables = supabaseTableNames(toolingTargetOf(plugins).schema)
|
|
257
255
|
.filter(
|
|
258
256
|
(table) => table !== SUPABASE_SETTINGS_TABLE && !keep.includes(table),
|
|
259
257
|
)
|
|
@@ -456,10 +454,14 @@ describe.sequential("supabase edge runtime acceptance", () => {
|
|
|
456
454
|
clientAccess: "public",
|
|
457
455
|
}).handlers,
|
|
458
456
|
),
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
457
|
+
plugins: [
|
|
458
|
+
insightsTestSuite({
|
|
459
|
+
createModel: (database) =>
|
|
460
|
+
createInsightsModel(
|
|
461
|
+
createDatabasePluginApis(database, [insights()]).insights,
|
|
462
|
+
),
|
|
463
|
+
}),
|
|
464
|
+
],
|
|
463
465
|
});
|
|
464
466
|
|
|
465
467
|
it("returns one canonical Channel row under concurrent inserts", async () => {
|
|
@@ -728,7 +730,7 @@ describe.sequential("supabase edge runtime acceptance", () => {
|
|
|
728
730
|
db: createDatabaseEngine({
|
|
729
731
|
adapter:
|
|
730
732
|
latencyMs > 0 ? withAdapterLatency(adapter, latencyMs) : adapter,
|
|
731
|
-
schema:
|
|
733
|
+
schema: toolingTargetOf([insights()]).schema,
|
|
732
734
|
...(retry === undefined ? {} : { retry }),
|
|
733
735
|
}).database(module),
|
|
734
736
|
// Insights never reads core.
|
|
@@ -46,13 +46,31 @@ CREATE TABLE IF NOT EXISTS "hot_updater_v1_bundle_events__byBundle" ("platform"
|
|
|
46
46
|
|
|
47
47
|
CREATE INDEX IF NOT EXISTS "hot_updater_v1_bundle_events_byDay" ON "hot_updater_v1_bundle_events" ("day", "received_at_ms", "id");
|
|
48
48
|
|
|
49
|
-
CREATE TABLE IF NOT EXISTS "hot_updater_v1_bundle_event_heads" ("install_id" varchar(255) COLLATE "C" NOT NULL, "id" varchar(36) COLLATE "C" NOT NULL, "type" varchar(32) COLLATE "C" NOT NULL, "user_id" varchar(255) COLLATE "C", "from_release_id" varchar(36) COLLATE "C", "from_bundle_id" varchar(36) COLLATE "C", "to_release_id" varchar(36) COLLATE "C", "to_bundle_id" varchar(36) COLLATE "C" NOT NULL, "platform" varchar(16) COLLATE "C" NOT NULL, "app_version" text COLLATE "C" NOT NULL, "channel" text COLLATE "C" NOT NULL, "metadata" jsonb NOT NULL, "received_at_ms" bigint NOT NULL, "
|
|
49
|
+
CREATE TABLE IF NOT EXISTS "hot_updater_v1_bundle_event_heads" ("install_id" varchar(255) COLLATE "C" NOT NULL, "id" varchar(36) COLLATE "C" NOT NULL, "type" varchar(32) COLLATE "C" NOT NULL, "user_id" varchar(255) COLLATE "C", "from_release_id" varchar(36) COLLATE "C", "from_bundle_id" varchar(36) COLLATE "C", "to_release_id" varchar(36) COLLATE "C", "to_bundle_id" varchar(36) COLLATE "C" NOT NULL, "platform" varchar(16) COLLATE "C" NOT NULL, "app_version" text COLLATE "C" NOT NULL, "channel" text COLLATE "C" NOT NULL, "metadata" jsonb NOT NULL, "received_at_ms" bigint NOT NULL, "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("install_id"));
|
|
50
50
|
|
|
51
51
|
CREATE INDEX IF NOT EXISTS "hot_updater_v1_bundle_event_heads_byUser" ON "hot_updater_v1_bundle_event_heads" ("user_id", "install_id");
|
|
52
52
|
|
|
53
|
-
CREATE
|
|
53
|
+
CREATE INDEX IF NOT EXISTS "hot_updater_v1_bundle_event_heads__retention" ON "hot_updater_v1_bundle_event_heads" ("received_at_ms", "install_id");
|
|
54
54
|
|
|
55
|
-
CREATE TABLE IF NOT EXISTS "
|
|
55
|
+
CREATE TABLE IF NOT EXISTS "hot_updater_v1_insights_overview" ("identity" varchar(32) COLLATE "C" NOT NULL, "bucket_start_ms" bigint NOT NULL, "_shard" bigint NOT NULL, "downloads" bigint NOT NULL, "launches" bigint NOT NULL, "failed_launches" bigint NOT NULL, "failed_updates" bigint NOT NULL, "patch_downloads" bigint NOT NULL, "patch_fallbacks" bigint NOT NULL, "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("identity", "bucket_start_ms", "_shard"));
|
|
56
|
+
|
|
57
|
+
CREATE INDEX IF NOT EXISTS "hot_updater_v1_insights_overview__retention" ON "hot_updater_v1_insights_overview" ("bucket_start_ms", "identity", "_shard");
|
|
58
|
+
|
|
59
|
+
CREATE TABLE IF NOT EXISTS "hot_updater_v1_insights_sketches" ("identity" varchar(32) COLLATE "C" NOT NULL, "bucket_start_ms" bigint NOT NULL, "_shard" bigint NOT NULL, "launch_users" text COLLATE "C", "activity_users" text COLLATE "C", "failed_users" text COLLATE "C", "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("identity", "bucket_start_ms", "_shard"));
|
|
60
|
+
|
|
61
|
+
CREATE INDEX IF NOT EXISTS "hot_updater_v1_insights_sketches__retention" ON "hot_updater_v1_insights_sketches" ("bucket_start_ms", "identity", "_shard");
|
|
62
|
+
|
|
63
|
+
CREATE TABLE IF NOT EXISTS "hot_updater_v1_insights_overview_daily" ("identity" varchar(32) COLLATE "C" NOT NULL, "bucket_start_ms" bigint NOT NULL, "_shard" bigint NOT NULL, "downloads" bigint NOT NULL, "launches" bigint NOT NULL, "failed_launches" bigint NOT NULL, "failed_updates" bigint NOT NULL, "patch_downloads" bigint NOT NULL, "patch_fallbacks" bigint NOT NULL, "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("identity", "bucket_start_ms", "_shard"));
|
|
64
|
+
|
|
65
|
+
CREATE INDEX IF NOT EXISTS "hot_updater_v1_insights_overview_daily__retention" ON "hot_updater_v1_insights_overview_daily" ("bucket_start_ms", "identity", "_shard");
|
|
66
|
+
|
|
67
|
+
CREATE TABLE IF NOT EXISTS "hot_updater_v1_insights_sketches_daily" ("identity" varchar(32) COLLATE "C" NOT NULL, "bucket_start_ms" bigint NOT NULL, "_shard" bigint NOT NULL, "launch_users" text COLLATE "C", "activity_users" text COLLATE "C", "failed_users" text COLLATE "C", "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("identity", "bucket_start_ms", "_shard"));
|
|
68
|
+
|
|
69
|
+
CREATE INDEX IF NOT EXISTS "hot_updater_v1_insights_sketches_daily__retention" ON "hot_updater_v1_insights_sketches_daily" ("bucket_start_ms", "identity", "_shard");
|
|
70
|
+
|
|
71
|
+
CREATE TABLE IF NOT EXISTS "hot_updater_v1_insights_overview_lifetime" ("identity" varchar(32) COLLATE "C" NOT NULL, "bucket_start_ms" bigint NOT NULL, "_shard" bigint NOT NULL, "downloads" bigint NOT NULL, "launches" bigint NOT NULL, "failed_launches" bigint NOT NULL, "failed_updates" bigint NOT NULL, "patch_downloads" bigint NOT NULL, "patch_fallbacks" bigint NOT NULL, "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("identity", "bucket_start_ms", "_shard"));
|
|
72
|
+
|
|
73
|
+
CREATE TABLE IF NOT EXISTS "hot_updater_v1_insights_sketches_lifetime" ("identity" varchar(32) COLLATE "C" NOT NULL, "bucket_start_ms" bigint NOT NULL, "_shard" bigint NOT NULL, "failed_users" text COLLATE "C", "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("identity", "bucket_start_ms", "_shard"));
|
|
56
74
|
|
|
57
75
|
CREATE TABLE IF NOT EXISTS "hot_updater_v1_insights_distribution" ("channel" text COLLATE "C" NOT NULL, "platform" varchar(16) COLLATE "C" NOT NULL, "app_version" text COLLATE "C" NOT NULL, "release_id" varchar(36) COLLATE "C" NOT NULL, "bucket_start_ms" bigint NOT NULL, "_shard" bigint NOT NULL, "latest_installations" bigint NOT NULL, "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("channel", "platform", "app_version", "release_id", "bucket_start_ms", "_shard"));
|
|
58
76
|
|
|
@@ -60,10 +78,20 @@ CREATE INDEX IF NOT EXISTS "hot_updater_v1_insights_distribution_byScope" ON "ho
|
|
|
60
78
|
|
|
61
79
|
CREATE INDEX IF NOT EXISTS "hot_updater_v1_insights_distribution_byVersion" ON "hot_updater_v1_insights_distribution" ("channel", "platform", "app_version", "bucket_start_ms", "release_id", "_shard");
|
|
62
80
|
|
|
81
|
+
CREATE INDEX IF NOT EXISTS "hot_updater_v1_insights_distribution__retention" ON "hot_updater_v1_insights_distribution" ("bucket_start_ms", "channel", "platform", "app_version", "release_id", "_shard");
|
|
82
|
+
|
|
63
83
|
CREATE TABLE IF NOT EXISTS "hot_updater_v1_insights_latest_by_bundle" ("platform" varchar(16) COLLATE "C" NOT NULL, "channel" text COLLATE "C" NOT NULL, "bundle_field" varchar(16) COLLATE "C" NOT NULL, "bundle_id" varchar(36) COLLATE "C" NOT NULL, "type" varchar(32) COLLATE "C" NOT NULL, "bucket_start_ms" bigint NOT NULL, "_shard" bigint NOT NULL, "installations" bigint NOT NULL, "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("platform", "channel", "bundle_field", "bundle_id", "type", "bucket_start_ms", "_shard"));
|
|
64
84
|
|
|
85
|
+
CREATE INDEX IF NOT EXISTS "hot_updater_v1_insights_latest_by_bundle__retention" ON "hot_updater_v1_insights_latest_by_bundle" ("bucket_start_ms", "platform", "channel", "bundle_field", "bundle_id", "type", "_shard");
|
|
86
|
+
|
|
65
87
|
CREATE TABLE IF NOT EXISTS "hot_updater_v1_insights_outcomes" ("platform" varchar(16) COLLATE "C" NOT NULL, "channel" text COLLATE "C" NOT NULL, "type" varchar(32) COLLATE "C" NOT NULL, "bundle_ref" varchar(41) COLLATE "C" NOT NULL, "bucket_start_ms" bigint NOT NULL, "_shard" bigint NOT NULL, "events" bigint NOT NULL, "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("platform", "channel", "type", "bundle_ref", "bucket_start_ms", "_shard"));
|
|
66
88
|
|
|
89
|
+
CREATE INDEX IF NOT EXISTS "hot_updater_v1_insights_outcomes__retention" ON "hot_updater_v1_insights_outcomes" ("bucket_start_ms", "platform", "channel", "type", "bundle_ref", "_shard");
|
|
90
|
+
|
|
91
|
+
CREATE TABLE IF NOT EXISTS "hot_updater_v1_insights_failures" ("platform" varchar(16) COLLATE "C" NOT NULL, "channel" text COLLATE "C" NOT NULL, "bucket_start_ms" bigint NOT NULL, "release_id" varchar(36) COLLATE "C" NOT NULL, "stage" varchar(16) COLLATE "C" NOT NULL, "reason" varchar(64) COLLATE "C" NOT NULL, "detail" varchar(160) COLLATE "C" NOT NULL, "_shard" bigint NOT NULL, "events" bigint NOT NULL, "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("platform", "channel", "bucket_start_ms", "release_id", "stage", "reason", "detail", "_shard"));
|
|
92
|
+
|
|
93
|
+
CREATE INDEX IF NOT EXISTS "hot_updater_v1_insights_failures__retention" ON "hot_updater_v1_insights_failures" ("bucket_start_ms", "platform", "channel", "release_id", "stage", "reason", "detail", "_shard");
|
|
94
|
+
|
|
67
95
|
CREATE TABLE IF NOT EXISTS "hot_updater_v1_api_keys" ("id" varchar(255) COLLATE "C" NOT NULL, "hash" varchar(64) COLLATE "C" NOT NULL, "name" varchar(64) COLLATE "C" NOT NULL, "prefix" varchar(16) COLLATE "C" NOT NULL, "role" varchar(16) COLLATE "C" NOT NULL, "created_at_ms" bigint NOT NULL, "revoked_at_ms" bigint, "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("id"));
|
|
68
96
|
|
|
69
97
|
CREATE INDEX IF NOT EXISTS "hot_updater_v1_api_keys_byCreated" ON "hot_updater_v1_api_keys" ("created_at_ms", "id");
|
|
@@ -94,12 +122,22 @@ ALTER TABLE "hot_updater_v1_insights_overview" ENABLE ROW LEVEL SECURITY;
|
|
|
94
122
|
|
|
95
123
|
ALTER TABLE "hot_updater_v1_insights_sketches" ENABLE ROW LEVEL SECURITY;
|
|
96
124
|
|
|
125
|
+
ALTER TABLE "hot_updater_v1_insights_overview_daily" ENABLE ROW LEVEL SECURITY;
|
|
126
|
+
|
|
127
|
+
ALTER TABLE "hot_updater_v1_insights_sketches_daily" ENABLE ROW LEVEL SECURITY;
|
|
128
|
+
|
|
129
|
+
ALTER TABLE "hot_updater_v1_insights_overview_lifetime" ENABLE ROW LEVEL SECURITY;
|
|
130
|
+
|
|
131
|
+
ALTER TABLE "hot_updater_v1_insights_sketches_lifetime" ENABLE ROW LEVEL SECURITY;
|
|
132
|
+
|
|
97
133
|
ALTER TABLE "hot_updater_v1_insights_distribution" ENABLE ROW LEVEL SECURITY;
|
|
98
134
|
|
|
99
135
|
ALTER TABLE "hot_updater_v1_insights_latest_by_bundle" ENABLE ROW LEVEL SECURITY;
|
|
100
136
|
|
|
101
137
|
ALTER TABLE "hot_updater_v1_insights_outcomes" ENABLE ROW LEVEL SECURITY;
|
|
102
138
|
|
|
139
|
+
ALTER TABLE "hot_updater_v1_insights_failures" ENABLE ROW LEVEL SECURITY;
|
|
140
|
+
|
|
103
141
|
ALTER TABLE "hot_updater_v1_api_keys" ENABLE ROW LEVEL SECURITY;
|
|
104
142
|
|
|
105
143
|
ALTER TABLE "hot_updater_v1_private_hot_updater_settings" ENABLE ROW LEVEL SECURITY;
|
|
@@ -125,7 +163,7 @@ BEGIN
|
|
|
125
163
|
FOR item IN SELECT value FROM jsonb_array_elements(p_statements) LOOP
|
|
126
164
|
statement := item->>'sql';
|
|
127
165
|
FOR target IN SELECT (regexp_matches(statement, '(?:FROM|INTO|UPDATE|JOIN) "([^"]+)"', 'g'))[1] LOOP
|
|
128
|
-
IF NOT target = ANY (ARRAY['hot_updater_v1_bundles', 'hot_updater_v1_bundle_patches', 'hot_updater_v1_releases', 'hot_updater_v1_release_catalogs', 'hot_updater_v1_channels', 'hot_updater_v1_bundle_totals', 'hot_updater_v1_bundle_events', 'hot_updater_v1_bundle_events__byBundle', 'hot_updater_v1_bundle_event_heads', 'hot_updater_v1_insights_overview', 'hot_updater_v1_insights_sketches', 'hot_updater_v1_insights_distribution', 'hot_updater_v1_insights_latest_by_bundle', 'hot_updater_v1_insights_outcomes', 'hot_updater_v1_api_keys', 'hot_updater_v1_private_hot_updater_settings', 'hot_updater_v1__hu_write']) THEN
|
|
166
|
+
IF NOT target = ANY (ARRAY['hot_updater_v1_bundles', 'hot_updater_v1_bundle_patches', 'hot_updater_v1_releases', 'hot_updater_v1_release_catalogs', 'hot_updater_v1_channels', 'hot_updater_v1_bundle_totals', 'hot_updater_v1_bundle_events', 'hot_updater_v1_bundle_events__byBundle', 'hot_updater_v1_bundle_event_heads', 'hot_updater_v1_insights_overview', 'hot_updater_v1_insights_sketches', 'hot_updater_v1_insights_overview_daily', 'hot_updater_v1_insights_sketches_daily', 'hot_updater_v1_insights_overview_lifetime', 'hot_updater_v1_insights_sketches_lifetime', 'hot_updater_v1_insights_distribution', 'hot_updater_v1_insights_latest_by_bundle', 'hot_updater_v1_insights_outcomes', 'hot_updater_v1_insights_failures', 'hot_updater_v1_api_keys', 'hot_updater_v1_private_hot_updater_settings', 'hot_updater_v1__hu_write']) THEN
|
|
129
167
|
RAISE EXCEPTION 'hot_updater_v1_apply: % is not a Hot Updater table', target USING ERRCODE = '42501';
|
|
130
168
|
END IF;
|
|
131
169
|
END LOOP;
|
|
@@ -160,6 +198,6 @@ INSERT INTO "hot_updater_v1_private_hot_updater_settings" ("key", "value", "_v")
|
|
|
160
198
|
|
|
161
199
|
INSERT INTO "hot_updater_v1_private_hot_updater_settings" ("key", "value", "_v") VALUES ('schema.core', '1.0.0', 0) ON CONFLICT ("key") DO UPDATE SET "value" = excluded."value";
|
|
162
200
|
|
|
163
|
-
INSERT INTO "hot_updater_v1_private_hot_updater_settings" ("key", "value", "_v") VALUES ('schema.insights', '1.
|
|
201
|
+
INSERT INTO "hot_updater_v1_private_hot_updater_settings" ("key", "value", "_v") VALUES ('schema.insights', '1.2.0', 0) ON CONFLICT ("key") DO UPDATE SET "value" = excluded."value";
|
|
164
202
|
|
|
165
203
|
INSERT INTO "hot_updater_v1_private_hot_updater_settings" ("key", "value", "_v") VALUES ('schema.apiKeys', '1.0.0', 0) ON CONFLICT ("key") DO UPDATE SET "value" = excluded."value";
|
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import { PGlite } from "@electric-sql/pglite";
|
|
5
5
|
import { createHotUpdater } from "@hot-updater/server";
|
|
6
|
+
import { toolingTargetOf } from "@hot-updater/server/database";
|
|
6
7
|
import {
|
|
7
8
|
createDatabasePluginApis,
|
|
8
9
|
createMigrator,
|
|
@@ -13,12 +14,14 @@ import {
|
|
|
13
14
|
createInsightsModel,
|
|
14
15
|
insights,
|
|
15
16
|
} from "@hot-updater/server/plugins/insights";
|
|
17
|
+
import { insightsTestSuite } from "@hot-updater/server/plugins/insights/testing";
|
|
16
18
|
import {
|
|
17
19
|
setupDatabaseTestSuite,
|
|
18
20
|
startHttpTestServer,
|
|
19
21
|
} from "@hot-updater/test-utils";
|
|
20
22
|
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
|
21
23
|
|
|
24
|
+
import { plugins } from "../src/plugins";
|
|
22
25
|
import { supabaseDatabase } from "../src/supabaseDatabase";
|
|
23
26
|
import { toApplyStatement } from "../src/supabaseExecutor";
|
|
24
27
|
import { supabaseDatabase as supabaseToolingDatabase } from "../src/supabaseMigration";
|
|
@@ -31,6 +34,8 @@ import {
|
|
|
31
34
|
|
|
32
35
|
const MIGRATIONS = path.resolve("plugins/supabase/supabase/migrations");
|
|
33
36
|
const MIGRATION = path.join(MIGRATIONS, "20260818000000_hot-updater_1.0.0.sql");
|
|
37
|
+
/** The managed server's tables: core's, and its plugins' (Insights and API keys). */
|
|
38
|
+
const managed = toolingTargetOf(plugins);
|
|
34
39
|
|
|
35
40
|
const state = vi.hoisted(() => ({ db: undefined as PGlite | undefined }));
|
|
36
41
|
|
|
@@ -74,14 +79,16 @@ const apply = (db: PGlite, sql: string) =>
|
|
|
74
79
|
describe("Supabase schema", () => {
|
|
75
80
|
it("checks in exactly the generated migration as the only one", async () => {
|
|
76
81
|
if (process.env.HOT_UPDATER_UPDATE_SQL === "1") {
|
|
77
|
-
await fs.writeFile(MIGRATION, supabaseSchemaSql());
|
|
82
|
+
await fs.writeFile(MIGRATION, supabaseSchemaSql(managed));
|
|
78
83
|
}
|
|
79
84
|
const files = (await fs.readdir(MIGRATIONS)).filter((file) =>
|
|
80
85
|
file.endsWith(".sql"),
|
|
81
86
|
);
|
|
82
87
|
expect(files).toEqual([path.basename(MIGRATION)]);
|
|
83
88
|
// Regenerate with HOT_UPDATER_UPDATE_SQL=1 after the schema changes.
|
|
84
|
-
expect(await fs.readFile(MIGRATION, "utf8")).toBe(
|
|
89
|
+
expect(await fs.readFile(MIGRATION, "utf8")).toBe(
|
|
90
|
+
supabaseSchemaSql(managed),
|
|
91
|
+
);
|
|
85
92
|
});
|
|
86
93
|
|
|
87
94
|
it("creates namespaced tables with row-level security beside a v0 schema it leaves alone", async () => {
|
|
@@ -96,7 +103,7 @@ describe("Supabase schema", () => {
|
|
|
96
103
|
name.startsWith("hot_updater_v1_"),
|
|
97
104
|
);
|
|
98
105
|
expect(ours.map(({ name }) => name)).toEqual(
|
|
99
|
-
supabaseTableNames().toSorted(),
|
|
106
|
+
supabaseTableNames(managed.schema).toSorted(),
|
|
100
107
|
);
|
|
101
108
|
expect(ours.every(({ secured }) => secured)).toBe(true);
|
|
102
109
|
await expect(
|
|
@@ -254,10 +261,14 @@ describe("supabaseDatabase over the apply RPC", () => {
|
|
|
254
261
|
clientAccess: "public",
|
|
255
262
|
}).handlers,
|
|
256
263
|
),
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
264
|
+
plugins: [
|
|
265
|
+
insightsTestSuite({
|
|
266
|
+
createModel: (database) =>
|
|
267
|
+
createInsightsModel(
|
|
268
|
+
createDatabasePluginApis(database, [insights()]).insights,
|
|
269
|
+
),
|
|
270
|
+
}),
|
|
271
|
+
],
|
|
261
272
|
name: "supabaseDatabase (PGlite, apply RPC)",
|
|
262
273
|
migrate: () => undefined,
|
|
263
274
|
createDatabase: () =>
|
|
@@ -266,7 +277,7 @@ describe("supabaseDatabase over the apply RPC", () => {
|
|
|
266
277
|
supabaseServiceRoleKey: "service-role-key",
|
|
267
278
|
}),
|
|
268
279
|
reset: async () => {
|
|
269
|
-
const tables = supabaseTableNames()
|
|
280
|
+
const tables = supabaseTableNames(managed.schema)
|
|
270
281
|
.filter((table) => table !== SUPABASE_SETTINGS_TABLE)
|
|
271
282
|
.map((table) => `public."${table}"`);
|
|
272
283
|
await state.db!.exec(`TRUNCATE ${tables.join(", ")} CASCADE`);
|