@hot-updater/supabase 1.1.4 → 1.2.0-rc.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.
- package/dist/edge.cjs +1 -1
- package/dist/edge.mjs +1 -1
- package/dist/index.cjs +13 -2
- package/dist/index.mjs +13 -2
- package/dist/init/index.cjs +76 -58
- package/dist/init/index.mjs +76 -58
- package/dist/{supabaseStorage-C8xQ3nyM.mjs → supabaseStorage-D4jcGa-l.mjs} +13 -9
- package/dist/{supabaseStorage-DAl3pyb7.cjs → supabaseStorage-MqoRcaii.cjs} +13 -9
- package/package.json +8 -8
- package/supabase/migrations/0002_hot-updater_1.2.0.sql +73 -0
package/dist/edge.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_supabaseStorage = require("./supabaseStorage-
|
|
2
|
+
const require_supabaseStorage = require("./supabaseStorage-MqoRcaii.cjs");
|
|
3
3
|
exports.supabaseDatabase = require_supabaseStorage.supabaseDatabase;
|
|
4
4
|
exports.supabaseStorage = require_supabaseStorage.supabaseStorage;
|
package/dist/edge.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as supabaseDatabase, t as supabaseStorage } from "./supabaseStorage-
|
|
1
|
+
import { n as supabaseDatabase, t as supabaseStorage } from "./supabaseStorage-D4jcGa-l.mjs";
|
|
2
2
|
export { supabaseDatabase, supabaseStorage };
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_supabaseStorage = require("./supabaseStorage-
|
|
2
|
+
const require_supabaseStorage = require("./supabaseStorage-MqoRcaii.cjs");
|
|
3
3
|
let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
|
|
4
4
|
//#region src/supabaseSchema.ts
|
|
5
5
|
/** Every table the apply RPC may name: models, index tables, settings, and the write guard. */
|
|
@@ -118,10 +118,20 @@ const supabaseSchemaStatements = ({ schema, settings: expected } = _hot_updater_
|
|
|
118
118
|
...settings
|
|
119
119
|
];
|
|
120
120
|
};
|
|
121
|
-
const
|
|
121
|
+
const HEADER = "-- HotUpdater.schema\n\n";
|
|
122
|
+
const toSql = (statements) => `${HEADER}${statements.map((statement) => `${statement};`).join("\n\n")}\n`;
|
|
123
|
+
const supabaseSchemaSql = (target) => toSql(supabaseSchemaStatements(target));
|
|
122
124
|
//#endregion
|
|
123
125
|
//#region src/supabaseMigration.ts
|
|
124
126
|
/**
|
|
127
|
+
* The resource this database reaches, without credentials, under the key
|
|
128
|
+
* `hot-updater doctor` reads to tell the database `hot-updater init` saved
|
|
129
|
+
* from another (a convention CLAUDE.md documents).
|
|
130
|
+
*/
|
|
131
|
+
const DATABASE_TARGET = Symbol.for("@hot-updater/database-target");
|
|
132
|
+
/** `target` under {@link DATABASE_TARGET}, to spread into the database. */
|
|
133
|
+
const marked = (target) => ({ [DATABASE_TARGET]: target });
|
|
134
|
+
/**
|
|
125
135
|
* Hot Updater's database on Supabase, with the migration `hot-updater db
|
|
126
136
|
* generate` writes to `supabase/migrations`: core's tables, the server's
|
|
127
137
|
* plugin tables, the apply RPC that may name them, and their settings rows.
|
|
@@ -129,6 +139,7 @@ const supabaseSchemaSql = (target) => `-- HotUpdater.schema\n\n${supabaseSchemaS
|
|
|
129
139
|
*/
|
|
130
140
|
const supabaseDatabase = (config) => ({
|
|
131
141
|
...require_supabaseStorage.supabaseDatabase(config),
|
|
142
|
+
...marked(`supabase/${new URL(config.supabaseUrl).hostname}`),
|
|
132
143
|
generateSchema: ((version, _name, target = _hot_updater_plugin_core.coreTarget) => {
|
|
133
144
|
if (version !== "latest" && version !== _hot_updater_plugin_core.coreSettings["schema.core"]) throw new Error(`Invalid version ${version}`);
|
|
134
145
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14);
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as SUPABASE_TABLE_PREFIX, n as supabaseDatabase$1, r as SUPABASE_APPLY_FUNCTION, t as supabaseStorage } from "./supabaseStorage-
|
|
1
|
+
import { i as SUPABASE_TABLE_PREFIX, n as supabaseDatabase$1, r as SUPABASE_APPLY_FUNCTION, t as supabaseStorage } from "./supabaseStorage-D4jcGa-l.mjs";
|
|
2
2
|
import { SETTINGS_TABLE, WRITE_GUARD_TABLE, coreSettings, coreTarget, createTableStatements, generateEngineSql, isMultiIndex } from "@hot-updater/plugin-core";
|
|
3
3
|
//#region src/supabaseSchema.ts
|
|
4
4
|
/** Every table the apply RPC may name: models, index tables, settings, and the write guard. */
|
|
@@ -117,10 +117,20 @@ const supabaseSchemaStatements = ({ schema, settings: expected } = coreTarget) =
|
|
|
117
117
|
...settings
|
|
118
118
|
];
|
|
119
119
|
};
|
|
120
|
-
const
|
|
120
|
+
const HEADER = "-- HotUpdater.schema\n\n";
|
|
121
|
+
const toSql = (statements) => `${HEADER}${statements.map((statement) => `${statement};`).join("\n\n")}\n`;
|
|
122
|
+
const supabaseSchemaSql = (target) => toSql(supabaseSchemaStatements(target));
|
|
121
123
|
//#endregion
|
|
122
124
|
//#region src/supabaseMigration.ts
|
|
123
125
|
/**
|
|
126
|
+
* The resource this database reaches, without credentials, under the key
|
|
127
|
+
* `hot-updater doctor` reads to tell the database `hot-updater init` saved
|
|
128
|
+
* from another (a convention CLAUDE.md documents).
|
|
129
|
+
*/
|
|
130
|
+
const DATABASE_TARGET = Symbol.for("@hot-updater/database-target");
|
|
131
|
+
/** `target` under {@link DATABASE_TARGET}, to spread into the database. */
|
|
132
|
+
const marked = (target) => ({ [DATABASE_TARGET]: target });
|
|
133
|
+
/**
|
|
124
134
|
* Hot Updater's database on Supabase, with the migration `hot-updater db
|
|
125
135
|
* generate` writes to `supabase/migrations`: core's tables, the server's
|
|
126
136
|
* plugin tables, the apply RPC that may name them, and their settings rows.
|
|
@@ -128,6 +138,7 @@ const supabaseSchemaSql = (target) => `-- HotUpdater.schema\n\n${supabaseSchemaS
|
|
|
128
138
|
*/
|
|
129
139
|
const supabaseDatabase = (config) => ({
|
|
130
140
|
...supabaseDatabase$1(config),
|
|
141
|
+
...marked(`supabase/${new URL(config.supabaseUrl).hostname}`),
|
|
131
142
|
generateSchema: ((version, _name, target = coreTarget) => {
|
|
132
143
|
if (version !== "latest" && version !== coreSettings["schema.core"]) throw new Error(`Invalid version ${version}`);
|
|
133
144
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14);
|
package/dist/init/index.cjs
CHANGED
|
@@ -6370,11 +6370,14 @@ const toApplyStatement = ({ sql, params }) => {
|
|
|
6370
6370
|
const withCode = (error) => Object.assign(new Error(error.message, { cause: error }), { code: error.code === "PGRST202" || error.code === "PGRST205" ? "42P01" : error.code });
|
|
6371
6371
|
/**
|
|
6372
6372
|
* The SQL core's executor over Supabase: every statement goes through the
|
|
6373
|
-
* apply RPC, and a write is one RPC call, so one transaction.
|
|
6373
|
+
* apply RPC, and a write is one RPC call, so one transaction. `client` may
|
|
6374
|
+
* be what creates the client, called on the first statement.
|
|
6374
6375
|
*/
|
|
6375
6376
|
const supabaseExecutor = (client) => {
|
|
6377
|
+
let created;
|
|
6378
|
+
const clientOf = () => typeof client === "function" ? created ??= client() : client;
|
|
6376
6379
|
const apply = async (statements) => {
|
|
6377
|
-
const { data, error } = await
|
|
6380
|
+
const { data, error } = await clientOf().rpc(SUPABASE_APPLY_FUNCTION, { p_statements: statements.map(toApplyStatement) });
|
|
6378
6381
|
if (error) throw withCode(error);
|
|
6379
6382
|
return data;
|
|
6380
6383
|
};
|
|
@@ -6397,7 +6400,7 @@ const supabaseExecutor = (client) => {
|
|
|
6397
6400
|
*/
|
|
6398
6401
|
const supabaseDatabase = (config) => {
|
|
6399
6402
|
const { migrations: _migrations, ...adapter } = (0, _hot_updater_plugin_core.createSqlAdapter)({
|
|
6400
|
-
executor: supabaseExecutor((0, _supabase_supabase_js.createClient)(config.supabaseUrl, resolveSupabaseServiceRoleKey(config))),
|
|
6403
|
+
executor: supabaseExecutor(() => (0, _supabase_supabase_js.createClient)(config.supabaseUrl, resolveSupabaseServiceRoleKey(config))),
|
|
6401
6404
|
tablePrefix: SUPABASE_TABLE_PREFIX
|
|
6402
6405
|
});
|
|
6403
6406
|
return (0, _hot_updater_plugin_core.createEngineDatabase)({
|
|
@@ -6683,12 +6686,18 @@ const isSupabaseDatabaseAuthError = (err) => {
|
|
|
6683
6686
|
const stderr = err.stderr;
|
|
6684
6687
|
return typeof stderr === "string" && SUPABASE_DATABASE_AUTH_ERROR_PATTERNS.some((pattern) => pattern.test(stderr));
|
|
6685
6688
|
};
|
|
6686
|
-
|
|
6689
|
+
/** What a failed database command reports: a bad password, or the CLI's output. */
|
|
6690
|
+
const supabaseDatabaseCommandErrorMessage = (err, { dbPassword, stderrInherited = false }) => {
|
|
6687
6691
|
if (err instanceof ExecaError) {
|
|
6688
|
-
if (dbPassword && isSupabaseDatabaseAuthError(err))
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
}
|
|
6692
|
+
if (dbPassword && isSupabaseDatabaseAuthError(err)) return SUPABASE_DATABASE_CONNECTION_ERROR;
|
|
6693
|
+
if (!stderrInherited && err.stderr) return err.stderr;
|
|
6694
|
+
return err.message;
|
|
6695
|
+
}
|
|
6696
|
+
return err instanceof Error ? err.message : String(err);
|
|
6697
|
+
};
|
|
6698
|
+
const handleSupabaseDatabaseCommandError = (err, options) => {
|
|
6699
|
+
if (err instanceof ExecaError) _hot_updater_cli_tools.p.log.error(supabaseDatabaseCommandErrorMessage(err, options));
|
|
6700
|
+
else console.error(err);
|
|
6692
6701
|
process.exit(1);
|
|
6693
6702
|
};
|
|
6694
6703
|
const confirmSupabaseDatabaseMigrations = async ({ nonInteractive }) => {
|
|
@@ -6698,71 +6707,80 @@ const confirmSupabaseDatabaseMigrations = async ({ nonInteractive }) => {
|
|
|
6698
6707
|
initialValue: true
|
|
6699
6708
|
}) === true;
|
|
6700
6709
|
};
|
|
6701
|
-
const
|
|
6710
|
+
const runSupabaseLink = async (workdir, { accessToken, projectId, dbPassword }) => {
|
|
6711
|
+
await fs_promises.default.writeFile(path.default.join(workdir, "supabase", "config.toml"), (0, _hot_updater_cli_tools.transformTemplate)(SUPABASE_CONFIG_TEMPLATE, { projectId }));
|
|
6712
|
+
await execa("npx", [
|
|
6713
|
+
"supabase",
|
|
6714
|
+
"link",
|
|
6715
|
+
"--project-ref",
|
|
6716
|
+
projectId,
|
|
6717
|
+
"--workdir",
|
|
6718
|
+
workdir
|
|
6719
|
+
], {
|
|
6720
|
+
cwd: workdir,
|
|
6721
|
+
env: getSupabaseCommandEnv(accessToken, dbPassword),
|
|
6722
|
+
input: "",
|
|
6723
|
+
stdio: [
|
|
6724
|
+
"pipe",
|
|
6725
|
+
"pipe",
|
|
6726
|
+
"pipe"
|
|
6727
|
+
]
|
|
6728
|
+
});
|
|
6729
|
+
};
|
|
6730
|
+
const linkSupabase = async (workdir, options) => {
|
|
6702
6731
|
const spinner = _hot_updater_cli_tools.p.spinner();
|
|
6703
6732
|
try {
|
|
6704
|
-
await fs_promises.default.writeFile(path.default.join(workdir, "supabase", "config.toml"), (0, _hot_updater_cli_tools.transformTemplate)(SUPABASE_CONFIG_TEMPLATE, { projectId }));
|
|
6705
6733
|
spinner.start("Linking Supabase...");
|
|
6706
|
-
await
|
|
6707
|
-
"supabase",
|
|
6708
|
-
"link",
|
|
6709
|
-
"--project-ref",
|
|
6710
|
-
projectId,
|
|
6711
|
-
"--workdir",
|
|
6712
|
-
workdir
|
|
6713
|
-
], {
|
|
6714
|
-
cwd: workdir,
|
|
6715
|
-
env: getSupabaseCommandEnv(accessToken, dbPassword),
|
|
6716
|
-
input: "",
|
|
6717
|
-
stdio: [
|
|
6718
|
-
"pipe",
|
|
6719
|
-
"pipe",
|
|
6720
|
-
"pipe"
|
|
6721
|
-
]
|
|
6722
|
-
});
|
|
6734
|
+
await runSupabaseLink(workdir, options);
|
|
6723
6735
|
spinner.stop("Supabase linked ✔");
|
|
6724
6736
|
} catch (err) {
|
|
6725
6737
|
spinner.stop();
|
|
6726
|
-
handleSupabaseDatabaseCommandError(err instanceof Error ? err : new Error(String(err)), { dbPassword });
|
|
6738
|
+
handleSupabaseDatabaseCommandError(err instanceof Error ? err : new Error(String(err)), { dbPassword: options.dbPassword });
|
|
6727
6739
|
}
|
|
6728
6740
|
};
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6741
|
+
/** Pushes the workdir's migrations the linked project has not run, after its own history. */
|
|
6742
|
+
const runSupabasePush = async (workdir, { accessToken, dbPassword, quiet = false }) => {
|
|
6743
|
+
await execa("npx", [
|
|
6744
|
+
"supabase",
|
|
6745
|
+
"migration",
|
|
6746
|
+
"fetch",
|
|
6747
|
+
"--linked",
|
|
6748
|
+
"--yes",
|
|
6749
|
+
"--workdir",
|
|
6750
|
+
workdir
|
|
6751
|
+
], {
|
|
6752
|
+
cwd: workdir,
|
|
6753
|
+
env: getSupabaseCommandEnv(accessToken, dbPassword)
|
|
6754
|
+
}).catch((error) => {
|
|
6755
|
+
if (error instanceof ExecaError && [error.stderr, error.stdout].some((output) => typeof output === "string" && /relation \\?"supabase_migrations\.schema_migrations\\?" does not exist/.test(output))) return;
|
|
6756
|
+
throw error;
|
|
6757
|
+
});
|
|
6758
|
+
return execa("npx", [
|
|
6759
|
+
"supabase",
|
|
6760
|
+
"db",
|
|
6761
|
+
"push",
|
|
6762
|
+
"--include-all",
|
|
6763
|
+
"--yes",
|
|
6764
|
+
"--workdir",
|
|
6765
|
+
workdir
|
|
6766
|
+
], {
|
|
6767
|
+
cwd: workdir,
|
|
6768
|
+
env: getSupabaseCommandEnv(accessToken, dbPassword),
|
|
6769
|
+
...quiet ? { input: "" } : {
|
|
6757
6770
|
stderr: ["pipe", "inherit"],
|
|
6758
6771
|
stdin: "inherit",
|
|
6759
6772
|
stdout: "inherit"
|
|
6760
|
-
}
|
|
6773
|
+
}
|
|
6774
|
+
});
|
|
6775
|
+
};
|
|
6776
|
+
const pushDB = async (workdir, options) => {
|
|
6777
|
+
try {
|
|
6778
|
+
const dbPush = await runSupabasePush(workdir, options);
|
|
6761
6779
|
_hot_updater_cli_tools.p.log.success("DB pushed ✔");
|
|
6762
6780
|
return dbPush.stdout;
|
|
6763
6781
|
} catch (err) {
|
|
6764
6782
|
handleSupabaseDatabaseCommandError(err instanceof Error ? err : new Error(String(err)), {
|
|
6765
|
-
dbPassword,
|
|
6783
|
+
dbPassword: options.dbPassword,
|
|
6766
6784
|
stderrInherited: true
|
|
6767
6785
|
});
|
|
6768
6786
|
}
|
package/dist/init/index.mjs
CHANGED
|
@@ -6365,11 +6365,14 @@ const toApplyStatement = ({ sql, params }) => {
|
|
|
6365
6365
|
const withCode = (error) => Object.assign(new Error(error.message, { cause: error }), { code: error.code === "PGRST202" || error.code === "PGRST205" ? "42P01" : error.code });
|
|
6366
6366
|
/**
|
|
6367
6367
|
* The SQL core's executor over Supabase: every statement goes through the
|
|
6368
|
-
* apply RPC, and a write is one RPC call, so one transaction.
|
|
6368
|
+
* apply RPC, and a write is one RPC call, so one transaction. `client` may
|
|
6369
|
+
* be what creates the client, called on the first statement.
|
|
6369
6370
|
*/
|
|
6370
6371
|
const supabaseExecutor = (client) => {
|
|
6372
|
+
let created;
|
|
6373
|
+
const clientOf = () => typeof client === "function" ? created ??= client() : client;
|
|
6371
6374
|
const apply = async (statements) => {
|
|
6372
|
-
const { data, error } = await
|
|
6375
|
+
const { data, error } = await clientOf().rpc(SUPABASE_APPLY_FUNCTION, { p_statements: statements.map(toApplyStatement) });
|
|
6373
6376
|
if (error) throw withCode(error);
|
|
6374
6377
|
return data;
|
|
6375
6378
|
};
|
|
@@ -6392,7 +6395,7 @@ const supabaseExecutor = (client) => {
|
|
|
6392
6395
|
*/
|
|
6393
6396
|
const supabaseDatabase = (config) => {
|
|
6394
6397
|
const { migrations: _migrations, ...adapter } = createSqlAdapter({
|
|
6395
|
-
executor: supabaseExecutor(createClient(config.supabaseUrl, resolveSupabaseServiceRoleKey(config))),
|
|
6398
|
+
executor: supabaseExecutor(() => createClient(config.supabaseUrl, resolveSupabaseServiceRoleKey(config))),
|
|
6396
6399
|
tablePrefix: SUPABASE_TABLE_PREFIX
|
|
6397
6400
|
});
|
|
6398
6401
|
return createEngineDatabase({
|
|
@@ -6678,12 +6681,18 @@ const isSupabaseDatabaseAuthError = (err) => {
|
|
|
6678
6681
|
const stderr = err.stderr;
|
|
6679
6682
|
return typeof stderr === "string" && SUPABASE_DATABASE_AUTH_ERROR_PATTERNS.some((pattern) => pattern.test(stderr));
|
|
6680
6683
|
};
|
|
6681
|
-
|
|
6684
|
+
/** What a failed database command reports: a bad password, or the CLI's output. */
|
|
6685
|
+
const supabaseDatabaseCommandErrorMessage = (err, { dbPassword, stderrInherited = false }) => {
|
|
6682
6686
|
if (err instanceof ExecaError) {
|
|
6683
|
-
if (dbPassword && isSupabaseDatabaseAuthError(err))
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
}
|
|
6687
|
+
if (dbPassword && isSupabaseDatabaseAuthError(err)) return SUPABASE_DATABASE_CONNECTION_ERROR;
|
|
6688
|
+
if (!stderrInherited && err.stderr) return err.stderr;
|
|
6689
|
+
return err.message;
|
|
6690
|
+
}
|
|
6691
|
+
return err instanceof Error ? err.message : String(err);
|
|
6692
|
+
};
|
|
6693
|
+
const handleSupabaseDatabaseCommandError = (err, options) => {
|
|
6694
|
+
if (err instanceof ExecaError) p.log.error(supabaseDatabaseCommandErrorMessage(err, options));
|
|
6695
|
+
else console.error(err);
|
|
6687
6696
|
process.exit(1);
|
|
6688
6697
|
};
|
|
6689
6698
|
const confirmSupabaseDatabaseMigrations = async ({ nonInteractive }) => {
|
|
@@ -6693,71 +6702,80 @@ const confirmSupabaseDatabaseMigrations = async ({ nonInteractive }) => {
|
|
|
6693
6702
|
initialValue: true
|
|
6694
6703
|
}) === true;
|
|
6695
6704
|
};
|
|
6696
|
-
const
|
|
6705
|
+
const runSupabaseLink = async (workdir, { accessToken, projectId, dbPassword }) => {
|
|
6706
|
+
await fs.writeFile(path.join(workdir, "supabase", "config.toml"), transformTemplate(SUPABASE_CONFIG_TEMPLATE, { projectId }));
|
|
6707
|
+
await execa("npx", [
|
|
6708
|
+
"supabase",
|
|
6709
|
+
"link",
|
|
6710
|
+
"--project-ref",
|
|
6711
|
+
projectId,
|
|
6712
|
+
"--workdir",
|
|
6713
|
+
workdir
|
|
6714
|
+
], {
|
|
6715
|
+
cwd: workdir,
|
|
6716
|
+
env: getSupabaseCommandEnv(accessToken, dbPassword),
|
|
6717
|
+
input: "",
|
|
6718
|
+
stdio: [
|
|
6719
|
+
"pipe",
|
|
6720
|
+
"pipe",
|
|
6721
|
+
"pipe"
|
|
6722
|
+
]
|
|
6723
|
+
});
|
|
6724
|
+
};
|
|
6725
|
+
const linkSupabase = async (workdir, options) => {
|
|
6697
6726
|
const spinner = p.spinner();
|
|
6698
6727
|
try {
|
|
6699
|
-
await fs.writeFile(path.join(workdir, "supabase", "config.toml"), transformTemplate(SUPABASE_CONFIG_TEMPLATE, { projectId }));
|
|
6700
6728
|
spinner.start("Linking Supabase...");
|
|
6701
|
-
await
|
|
6702
|
-
"supabase",
|
|
6703
|
-
"link",
|
|
6704
|
-
"--project-ref",
|
|
6705
|
-
projectId,
|
|
6706
|
-
"--workdir",
|
|
6707
|
-
workdir
|
|
6708
|
-
], {
|
|
6709
|
-
cwd: workdir,
|
|
6710
|
-
env: getSupabaseCommandEnv(accessToken, dbPassword),
|
|
6711
|
-
input: "",
|
|
6712
|
-
stdio: [
|
|
6713
|
-
"pipe",
|
|
6714
|
-
"pipe",
|
|
6715
|
-
"pipe"
|
|
6716
|
-
]
|
|
6717
|
-
});
|
|
6729
|
+
await runSupabaseLink(workdir, options);
|
|
6718
6730
|
spinner.stop("Supabase linked ✔");
|
|
6719
6731
|
} catch (err) {
|
|
6720
6732
|
spinner.stop();
|
|
6721
|
-
handleSupabaseDatabaseCommandError(err instanceof Error ? err : new Error(String(err)), { dbPassword });
|
|
6733
|
+
handleSupabaseDatabaseCommandError(err instanceof Error ? err : new Error(String(err)), { dbPassword: options.dbPassword });
|
|
6722
6734
|
}
|
|
6723
6735
|
};
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6736
|
+
/** Pushes the workdir's migrations the linked project has not run, after its own history. */
|
|
6737
|
+
const runSupabasePush = async (workdir, { accessToken, dbPassword, quiet = false }) => {
|
|
6738
|
+
await execa("npx", [
|
|
6739
|
+
"supabase",
|
|
6740
|
+
"migration",
|
|
6741
|
+
"fetch",
|
|
6742
|
+
"--linked",
|
|
6743
|
+
"--yes",
|
|
6744
|
+
"--workdir",
|
|
6745
|
+
workdir
|
|
6746
|
+
], {
|
|
6747
|
+
cwd: workdir,
|
|
6748
|
+
env: getSupabaseCommandEnv(accessToken, dbPassword)
|
|
6749
|
+
}).catch((error) => {
|
|
6750
|
+
if (error instanceof ExecaError && [error.stderr, error.stdout].some((output) => typeof output === "string" && /relation \\?"supabase_migrations\.schema_migrations\\?" does not exist/.test(output))) return;
|
|
6751
|
+
throw error;
|
|
6752
|
+
});
|
|
6753
|
+
return execa("npx", [
|
|
6754
|
+
"supabase",
|
|
6755
|
+
"db",
|
|
6756
|
+
"push",
|
|
6757
|
+
"--include-all",
|
|
6758
|
+
"--yes",
|
|
6759
|
+
"--workdir",
|
|
6760
|
+
workdir
|
|
6761
|
+
], {
|
|
6762
|
+
cwd: workdir,
|
|
6763
|
+
env: getSupabaseCommandEnv(accessToken, dbPassword),
|
|
6764
|
+
...quiet ? { input: "" } : {
|
|
6752
6765
|
stderr: ["pipe", "inherit"],
|
|
6753
6766
|
stdin: "inherit",
|
|
6754
6767
|
stdout: "inherit"
|
|
6755
|
-
}
|
|
6768
|
+
}
|
|
6769
|
+
});
|
|
6770
|
+
};
|
|
6771
|
+
const pushDB = async (workdir, options) => {
|
|
6772
|
+
try {
|
|
6773
|
+
const dbPush = await runSupabasePush(workdir, options);
|
|
6756
6774
|
p.log.success("DB pushed ✔");
|
|
6757
6775
|
return dbPush.stdout;
|
|
6758
6776
|
} catch (err) {
|
|
6759
6777
|
handleSupabaseDatabaseCommandError(err instanceof Error ? err : new Error(String(err)), {
|
|
6760
|
-
dbPassword,
|
|
6778
|
+
dbPassword: options.dbPassword,
|
|
6761
6779
|
stderrInherited: true
|
|
6762
6780
|
});
|
|
6763
6781
|
}
|
|
@@ -37,11 +37,14 @@ const toApplyStatement = ({ sql, params }) => {
|
|
|
37
37
|
const withCode = (error) => Object.assign(new Error(error.message, { cause: error }), { code: error.code === "PGRST202" || error.code === "PGRST205" ? "42P01" : error.code });
|
|
38
38
|
/**
|
|
39
39
|
* The SQL core's executor over Supabase: every statement goes through the
|
|
40
|
-
* apply RPC, and a write is one RPC call, so one transaction.
|
|
40
|
+
* apply RPC, and a write is one RPC call, so one transaction. `client` may
|
|
41
|
+
* be what creates the client, called on the first statement.
|
|
41
42
|
*/
|
|
42
43
|
const supabaseExecutor = (client) => {
|
|
44
|
+
let created;
|
|
45
|
+
const clientOf = () => typeof client === "function" ? created ??= client() : client;
|
|
43
46
|
const apply = async (statements) => {
|
|
44
|
-
const { data, error } = await
|
|
47
|
+
const { data, error } = await clientOf().rpc(SUPABASE_APPLY_FUNCTION, { p_statements: statements.map(toApplyStatement) });
|
|
45
48
|
if (error) throw withCode(error);
|
|
46
49
|
return data;
|
|
47
50
|
};
|
|
@@ -64,7 +67,7 @@ const supabaseExecutor = (client) => {
|
|
|
64
67
|
*/
|
|
65
68
|
const supabaseDatabase = (config) => {
|
|
66
69
|
const { migrations: _migrations, ...adapter } = createSqlAdapter({
|
|
67
|
-
executor: supabaseExecutor(createClient(config.supabaseUrl, resolveSupabaseServiceRoleKey(config))),
|
|
70
|
+
executor: supabaseExecutor(() => createClient(config.supabaseUrl, resolveSupabaseServiceRoleKey(config))),
|
|
68
71
|
tablePrefix: SUPABASE_TABLE_PREFIX
|
|
69
72
|
});
|
|
70
73
|
return createEngineDatabase({
|
|
@@ -166,10 +169,11 @@ const utf8 = new TextEncoder();
|
|
|
166
169
|
*/
|
|
167
170
|
const toObjectName = (key) => key.replace(ESCAPED_CHARACTER, (character) => Array.from(utf8.encode(character), (byte) => `!${byte.toString(16).toUpperCase().padStart(2, "0")}`).join(""));
|
|
168
171
|
const supabaseStorage = (config) => {
|
|
169
|
-
|
|
172
|
+
let client;
|
|
173
|
+
const bucket = () => (client ??= createClient(config.supabaseUrl, resolveSupabaseServiceRoleKey(config))).storage.from(config.bucketName);
|
|
170
174
|
const getStorageKey = createStorageKeyBuilder(config.basePath);
|
|
171
175
|
const resolveSignedUrl = createSupabaseSignedUrlBatcher({
|
|
172
|
-
createSignedUrls: (_bucketName, keys, expiresIn) => bucket.createSignedUrls(keys, expiresIn),
|
|
176
|
+
createSignedUrls: (_bucketName, keys, expiresIn) => bucket().createSignedUrls(keys, expiresIn),
|
|
173
177
|
expiresIn: config.signedUrlExpiresIn ?? 3600,
|
|
174
178
|
formatObjectPath: (_bucketName, key) => key
|
|
175
179
|
});
|
|
@@ -183,7 +187,7 @@ const supabaseStorage = (config) => {
|
|
|
183
187
|
protocol: "supabase-storage",
|
|
184
188
|
async put({ key, body, contentLength, contentType }) {
|
|
185
189
|
const storageKey = getStorageKey(key);
|
|
186
|
-
const { error } = await bucket.upload(toObjectName(storageKey), body, {
|
|
190
|
+
const { error } = await bucket().upload(toObjectName(storageKey), body, {
|
|
187
191
|
contentType,
|
|
188
192
|
cacheControl: "max-age=31536000",
|
|
189
193
|
upsert: true,
|
|
@@ -199,7 +203,7 @@ const supabaseStorage = (config) => {
|
|
|
199
203
|
},
|
|
200
204
|
async get({ storageUri }) {
|
|
201
205
|
const { key } = parseAndValidate(storageUri);
|
|
202
|
-
const { data, error } = await bucket.download(toObjectName(key));
|
|
206
|
+
const { data, error } = await bucket().download(toObjectName(key));
|
|
203
207
|
if (error) {
|
|
204
208
|
if (await isMissingObjectError(error)) return { response: null };
|
|
205
209
|
throw new Error(`Failed to download storage object: ${error.message}`);
|
|
@@ -212,7 +216,7 @@ const supabaseStorage = (config) => {
|
|
|
212
216
|
},
|
|
213
217
|
async exists({ storageUri }) {
|
|
214
218
|
const { key } = parseAndValidate(storageUri);
|
|
215
|
-
const { data, error } = await bucket.exists(toObjectName(key));
|
|
219
|
+
const { data, error } = await bucket().exists(toObjectName(key));
|
|
216
220
|
if (error) {
|
|
217
221
|
if (isMissingExistsError(error)) return { exists: false };
|
|
218
222
|
throw error;
|
|
@@ -221,7 +225,7 @@ const supabaseStorage = (config) => {
|
|
|
221
225
|
},
|
|
222
226
|
async delete({ storageUri }) {
|
|
223
227
|
const { key } = parseAndValidate(storageUri);
|
|
224
|
-
const { error } = await bucket.remove([toObjectName(key)]);
|
|
228
|
+
const { error } = await bucket().remove([toObjectName(key)]);
|
|
225
229
|
if (error && !isNotFoundError(error)) throw new Error(`Failed to delete storage object: ${error.message}`);
|
|
226
230
|
return { deleted: true };
|
|
227
231
|
}
|
|
@@ -37,11 +37,14 @@ const toApplyStatement = ({ sql, params }) => {
|
|
|
37
37
|
const withCode = (error) => Object.assign(new Error(error.message, { cause: error }), { code: error.code === "PGRST202" || error.code === "PGRST205" ? "42P01" : error.code });
|
|
38
38
|
/**
|
|
39
39
|
* The SQL core's executor over Supabase: every statement goes through the
|
|
40
|
-
* apply RPC, and a write is one RPC call, so one transaction.
|
|
40
|
+
* apply RPC, and a write is one RPC call, so one transaction. `client` may
|
|
41
|
+
* be what creates the client, called on the first statement.
|
|
41
42
|
*/
|
|
42
43
|
const supabaseExecutor = (client) => {
|
|
44
|
+
let created;
|
|
45
|
+
const clientOf = () => typeof client === "function" ? created ??= client() : client;
|
|
43
46
|
const apply = async (statements) => {
|
|
44
|
-
const { data, error } = await
|
|
47
|
+
const { data, error } = await clientOf().rpc(SUPABASE_APPLY_FUNCTION, { p_statements: statements.map(toApplyStatement) });
|
|
45
48
|
if (error) throw withCode(error);
|
|
46
49
|
return data;
|
|
47
50
|
};
|
|
@@ -64,7 +67,7 @@ const supabaseExecutor = (client) => {
|
|
|
64
67
|
*/
|
|
65
68
|
const supabaseDatabase = (config) => {
|
|
66
69
|
const { migrations: _migrations, ...adapter } = (0, _hot_updater_plugin_core.createSqlAdapter)({
|
|
67
|
-
executor: supabaseExecutor((0, _supabase_supabase_js.createClient)(config.supabaseUrl, resolveSupabaseServiceRoleKey(config))),
|
|
70
|
+
executor: supabaseExecutor(() => (0, _supabase_supabase_js.createClient)(config.supabaseUrl, resolveSupabaseServiceRoleKey(config))),
|
|
68
71
|
tablePrefix: SUPABASE_TABLE_PREFIX
|
|
69
72
|
});
|
|
70
73
|
return (0, _hot_updater_plugin_core.createEngineDatabase)({
|
|
@@ -166,10 +169,11 @@ const utf8 = new TextEncoder();
|
|
|
166
169
|
*/
|
|
167
170
|
const toObjectName = (key) => key.replace(ESCAPED_CHARACTER, (character) => Array.from(utf8.encode(character), (byte) => `!${byte.toString(16).toUpperCase().padStart(2, "0")}`).join(""));
|
|
168
171
|
const supabaseStorage = (config) => {
|
|
169
|
-
|
|
172
|
+
let client;
|
|
173
|
+
const bucket = () => (client ??= (0, _supabase_supabase_js.createClient)(config.supabaseUrl, resolveSupabaseServiceRoleKey(config))).storage.from(config.bucketName);
|
|
170
174
|
const getStorageKey = (0, _hot_updater_plugin_core.createStorageKeyBuilder)(config.basePath);
|
|
171
175
|
const resolveSignedUrl = createSupabaseSignedUrlBatcher({
|
|
172
|
-
createSignedUrls: (_bucketName, keys, expiresIn) => bucket.createSignedUrls(keys, expiresIn),
|
|
176
|
+
createSignedUrls: (_bucketName, keys, expiresIn) => bucket().createSignedUrls(keys, expiresIn),
|
|
173
177
|
expiresIn: config.signedUrlExpiresIn ?? 3600,
|
|
174
178
|
formatObjectPath: (_bucketName, key) => key
|
|
175
179
|
});
|
|
@@ -183,7 +187,7 @@ const supabaseStorage = (config) => {
|
|
|
183
187
|
protocol: "supabase-storage",
|
|
184
188
|
async put({ key, body, contentLength, contentType }) {
|
|
185
189
|
const storageKey = getStorageKey(key);
|
|
186
|
-
const { error } = await bucket.upload(toObjectName(storageKey), body, {
|
|
190
|
+
const { error } = await bucket().upload(toObjectName(storageKey), body, {
|
|
187
191
|
contentType,
|
|
188
192
|
cacheControl: "max-age=31536000",
|
|
189
193
|
upsert: true,
|
|
@@ -199,7 +203,7 @@ const supabaseStorage = (config) => {
|
|
|
199
203
|
},
|
|
200
204
|
async get({ storageUri }) {
|
|
201
205
|
const { key } = parseAndValidate(storageUri);
|
|
202
|
-
const { data, error } = await bucket.download(toObjectName(key));
|
|
206
|
+
const { data, error } = await bucket().download(toObjectName(key));
|
|
203
207
|
if (error) {
|
|
204
208
|
if (await isMissingObjectError(error)) return { response: null };
|
|
205
209
|
throw new Error(`Failed to download storage object: ${error.message}`);
|
|
@@ -212,7 +216,7 @@ const supabaseStorage = (config) => {
|
|
|
212
216
|
},
|
|
213
217
|
async exists({ storageUri }) {
|
|
214
218
|
const { key } = parseAndValidate(storageUri);
|
|
215
|
-
const { data, error } = await bucket.exists(toObjectName(key));
|
|
219
|
+
const { data, error } = await bucket().exists(toObjectName(key));
|
|
216
220
|
if (error) {
|
|
217
221
|
if (isMissingExistsError(error)) return { exists: false };
|
|
218
222
|
throw error;
|
|
@@ -221,7 +225,7 @@ const supabaseStorage = (config) => {
|
|
|
221
225
|
},
|
|
222
226
|
async delete({ storageUri }) {
|
|
223
227
|
const { key } = parseAndValidate(storageUri);
|
|
224
|
-
const { error } = await bucket.remove([toObjectName(key)]);
|
|
228
|
+
const { error } = await bucket().remove([toObjectName(key)]);
|
|
225
229
|
if (error && !isNotFoundError(error)) throw new Error(`Failed to delete storage object: ${error.message}`);
|
|
226
230
|
return { deleted: true };
|
|
227
231
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/supabase",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.0-rc.0",
|
|
5
5
|
"description": "React Native OTA solution for self-hosted",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
@@ -51,10 +51,10 @@
|
|
|
51
51
|
"@supabase/supabase-js": "2.76.1",
|
|
52
52
|
"hono": "4.12.34",
|
|
53
53
|
"uuidv7": "^1.0.2",
|
|
54
|
-
"@hot-updater/protocol": "1.
|
|
55
|
-
"@hot-updater/
|
|
56
|
-
"@hot-updater/
|
|
57
|
-
"@hot-updater/server": "1.
|
|
54
|
+
"@hot-updater/protocol": "1.2.0-rc.0",
|
|
55
|
+
"@hot-updater/plugin-core": "1.2.0-rc.0",
|
|
56
|
+
"@hot-updater/cli-tools": "1.2.0-rc.0",
|
|
57
|
+
"@hot-updater/server": "1.2.0-rc.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@electric-sql/pglite": "0.4.1",
|
|
@@ -64,9 +64,9 @@
|
|
|
64
64
|
"execa": "9.5.2",
|
|
65
65
|
"@types/node": "^20",
|
|
66
66
|
"mime": "^4.0.4",
|
|
67
|
-
"@hot-updater/
|
|
68
|
-
"@hot-updater/
|
|
69
|
-
"@hot-updater/postgres": "1.
|
|
67
|
+
"@hot-updater/mock": "1.2.0-rc.0",
|
|
68
|
+
"@hot-updater/test-utils": "1.2.0-rc.0",
|
|
69
|
+
"@hot-updater/postgres": "1.2.0-rc.0"
|
|
70
70
|
},
|
|
71
71
|
"scripts": {
|
|
72
72
|
"build": "tsdown",
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
-- HotUpdater.schema
|
|
2
|
+
|
|
3
|
+
CREATE TABLE IF NOT EXISTS "hot_updater_v1_native_builds" ("id" varchar(36) COLLATE "C" NOT NULL, "scope_key" varchar(2048) COLLATE "C" NOT NULL, "platform" varchar(16) COLLATE "C" NOT NULL, "channel_id" varchar(1408) COLLATE "C" NOT NULL, "fingerprint_hash" varchar(255) COLLATE "C", "app_version" text COLLATE "C", "min_bundle_id" varchar(36) COLLATE "C" NOT NULL, "artifact_kind" varchar(16) COLLATE "C" NOT NULL, "artifact_storage_uri" text COLLATE "C" NOT NULL, "byte_size" bigint NOT NULL, "git_commit_hash" varchar(255) COLLATE "C", "created_at_ms" bigint NOT NULL, "build_key" varchar(69) COLLATE "C", "build_number" varchar(64) COLLATE "C", "submitted_to" varchar(16) COLLATE "C", "submitted_at_ms" bigint, "submitted_version" varchar(255) COLLATE "C", "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("id"));
|
|
4
|
+
|
|
5
|
+
CREATE INDEX IF NOT EXISTS "hot_updater_v1_native_builds_byScope" ON "hot_updater_v1_native_builds" ("scope_key", "id");
|
|
6
|
+
|
|
7
|
+
CREATE INDEX IF NOT EXISTS "hot_updater_v1_native_builds_byBuildKey" ON "hot_updater_v1_native_builds" ("scope_key", "build_key", "id");
|
|
8
|
+
|
|
9
|
+
CREATE INDEX IF NOT EXISTS "hot_updater_v1_native_builds_byChannel" ON "hot_updater_v1_native_builds" ("channel_id", "id");
|
|
10
|
+
|
|
11
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "hot_updater_v1_native_builds_min_bundle_id" ON "hot_updater_v1_native_builds" ("min_bundle_id");
|
|
12
|
+
|
|
13
|
+
ALTER TABLE "hot_updater_v1_native_builds" ENABLE ROW LEVEL SECURITY;
|
|
14
|
+
|
|
15
|
+
CREATE OR REPLACE FUNCTION public.hot_updater_v1_apply(p_statements jsonb)
|
|
16
|
+
RETURNS jsonb
|
|
17
|
+
LANGUAGE plpgsql
|
|
18
|
+
SECURITY INVOKER
|
|
19
|
+
SET search_path = public, pg_temp
|
|
20
|
+
AS $apply$
|
|
21
|
+
DECLARE
|
|
22
|
+
item jsonb;
|
|
23
|
+
statement text;
|
|
24
|
+
shape text;
|
|
25
|
+
target text;
|
|
26
|
+
found record;
|
|
27
|
+
rows jsonb;
|
|
28
|
+
changed bigint;
|
|
29
|
+
results jsonb := '[]'::jsonb;
|
|
30
|
+
BEGIN
|
|
31
|
+
FOR item IN SELECT value FROM jsonb_array_elements(p_statements) LOOP
|
|
32
|
+
statement := item->>'sql';
|
|
33
|
+
FOR target IN SELECT (regexp_matches(statement, '(?:FROM|INTO|UPDATE|JOIN) "([^"]+)"', 'g'))[1] LOOP
|
|
34
|
+
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_native_builds', '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_builtin_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_remote_config_active', 'hot_updater_v1_remote_config_versions', 'hot_updater_v1_private_hot_updater_settings', 'hot_updater_v1__hu_write']) THEN
|
|
35
|
+
RAISE EXCEPTION 'hot_updater_v1_apply: % is not a Hot Updater table', target USING ERRCODE = '42501';
|
|
36
|
+
END IF;
|
|
37
|
+
END LOOP;
|
|
38
|
+
shape := regexp_replace(regexp_replace(statement, '^INSERT INTO "[^"]+" \(', 'INSERT INTO ('), '"[^"]*"', '"', 'g');
|
|
39
|
+
IF shape !~ '^(SELECT|INSERT INTO|UPDATE|DELETE FROM) '
|
|
40
|
+
OR shape ~ '"[[:space:]]*\('
|
|
41
|
+
OR shape ~ '[;''\\]|--|/\*'
|
|
42
|
+
OR regexp_replace(shape, '\m(SELECT|FROM|WHERE|AND|OR|NOT|EXISTS|INSERT|INTO|VALUES|UPDATE|SET|DELETE|ORDER|BY|ASC|DESC|LIMIT|IS|NULL|ON|CONFLICT|DO|JOIN|FOR|bigint|double|precision|boolean|jsonb|i|b)\M', '', 'gi') !~ '^[[:space:]"(),.*=<>+0-9$:-]*$' THEN
|
|
43
|
+
RAISE EXCEPTION 'hot_updater_v1_apply: statement not allowed' USING ERRCODE = '42501';
|
|
44
|
+
END IF;
|
|
45
|
+
IF statement ~ '^SELECT ' THEN
|
|
46
|
+
rows := '[]'::jsonb;
|
|
47
|
+
FOR found IN EXECUTE statement USING item->'params' LOOP
|
|
48
|
+
rows := rows || jsonb_build_array(to_jsonb(found));
|
|
49
|
+
END LOOP;
|
|
50
|
+
results := results || jsonb_build_array(jsonb_build_object('rows', rows, 'changes', 0));
|
|
51
|
+
ELSE
|
|
52
|
+
EXECUTE statement USING item->'params';
|
|
53
|
+
GET DIAGNOSTICS changed = ROW_COUNT;
|
|
54
|
+
results := results || jsonb_build_array(jsonb_build_object('rows', '[]'::jsonb, 'changes', changed));
|
|
55
|
+
END IF;
|
|
56
|
+
END LOOP;
|
|
57
|
+
RETURN results;
|
|
58
|
+
END;
|
|
59
|
+
$apply$;
|
|
60
|
+
|
|
61
|
+
REVOKE EXECUTE ON FUNCTION public.hot_updater_v1_apply(jsonb) FROM PUBLIC, anon, authenticated;
|
|
62
|
+
|
|
63
|
+
GRANT EXECUTE ON FUNCTION public.hot_updater_v1_apply(jsonb) TO service_role;
|
|
64
|
+
|
|
65
|
+
INSERT INTO "hot_updater_v1_private_hot_updater_settings" ("key", "value", "_v") VALUES ('schema.engine', '1', 0) ON CONFLICT ("key") DO UPDATE SET "value" = excluded."value";
|
|
66
|
+
|
|
67
|
+
INSERT INTO "hot_updater_v1_private_hot_updater_settings" ("key", "value", "_v") VALUES ('schema.core', '1.2.0', 0) ON CONFLICT ("key") DO UPDATE SET "value" = excluded."value";
|
|
68
|
+
|
|
69
|
+
INSERT INTO "hot_updater_v1_private_hot_updater_settings" ("key", "value", "_v") VALUES ('schema.insights', '1.0.0', 0) ON CONFLICT ("key") DO UPDATE SET "value" = excluded."value";
|
|
70
|
+
|
|
71
|
+
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";
|
|
72
|
+
|
|
73
|
+
INSERT INTO "hot_updater_v1_private_hot_updater_settings" ("key", "value", "_v") VALUES ('schema.remoteConfig', '1.0.0', 0) ON CONFLICT ("key") DO UPDATE SET "value" = excluded."value";
|