@hot-updater/supabase 1.0.0-rc.2 → 1.0.0-rc.22
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 +4 -3
- package/dist/edge.d.cts +4 -4
- package/dist/edge.d.mts +4 -4
- package/dist/edge.mjs +2 -2
- package/dist/index.cjs +143 -3
- package/dist/index.d.cts +13 -4
- package/dist/index.d.mts +13 -4
- package/dist/index.mjs +141 -2
- package/dist/init/index.cjs +7593 -4
- package/dist/init/index.d.cts +97 -4
- package/dist/init/index.d.mts +97 -4
- package/dist/init/index.mjs +7578 -1
- package/dist/plugins-Dj-4Pk3j.mjs +240 -0
- package/dist/plugins-jpIkI_T2.cjs +269 -0
- package/dist/supabaseDatabase-DVb3-y2x.d.cts +726 -0
- package/dist/supabaseDatabase-DVb3-y2x.d.mts +726 -0
- package/package.json +9 -14
- package/supabase/edge-functions/index.ts +6 -2
- package/supabase/migrations/20260818000000_hot-updater_1.0.0.sql +190 -604
- package/dist/iac/index.cjs +0 -8096
- package/dist/iac/index.d.cts +0 -102
- package/dist/iac/index.d.mts +0 -102
- package/dist/iac/index.mjs +0 -8079
- package/dist/supabaseDatabase-3d90EtUD.d.cts +0 -11
- package/dist/supabaseDatabase-3d90EtUD.d.mts +0 -11
- package/dist/supabaseStorage-CbzHzqCa.cjs +0 -622
- package/dist/supabaseStorage-CjteZ2zX.mjs +0 -611
- package/supabase/edge-functions/runtime.docker.integration.spec.ts +0 -1183
- package/supabase/migrations.spec.ts +0 -281
package/dist/edge.cjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
2
|
+
const require_plugins = require("./plugins-jpIkI_T2.cjs");
|
|
3
3
|
//#region src/supabaseEdgeFunctionStorage.ts
|
|
4
|
-
const supabaseEdgeFunctionStorage = (config) =>
|
|
4
|
+
const supabaseEdgeFunctionStorage = (config) => require_plugins.supabaseStorage(config);
|
|
5
5
|
//#endregion
|
|
6
|
-
exports.
|
|
6
|
+
exports.plugins = require_plugins.plugins;
|
|
7
|
+
exports.supabaseDatabase = require_plugins.supabaseDatabase;
|
|
7
8
|
exports.supabaseStorage = supabaseEdgeFunctionStorage;
|
package/dist/edge.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as supabaseDatabase, t as SupabaseDatabaseConfig } from "./supabaseDatabase-
|
|
2
|
-
import {
|
|
1
|
+
import { i as plugins, n as supabaseDatabase, t as SupabaseDatabaseConfig } from "./supabaseDatabase-DVb3-y2x.cjs";
|
|
2
|
+
import { StorageAdapterWith } from "@hot-updater/plugin-core";
|
|
3
3
|
//#region src/supabaseEdgeFunctionStorage.d.ts
|
|
4
4
|
interface SupabaseEdgeFunctionStorageConfig {
|
|
5
5
|
supabaseUrl: string;
|
|
@@ -8,6 +8,6 @@ interface SupabaseEdgeFunctionStorageConfig {
|
|
|
8
8
|
basePath?: string;
|
|
9
9
|
signedUrlExpiresIn?: number;
|
|
10
10
|
}
|
|
11
|
-
declare const supabaseEdgeFunctionStorage: (config: SupabaseEdgeFunctionStorageConfig) =>
|
|
11
|
+
declare const supabaseEdgeFunctionStorage: (config: SupabaseEdgeFunctionStorageConfig) => StorageAdapterWith<"put" | "get" | "getDownloadUrl" | "exists" | "delete">;
|
|
12
12
|
//#endregion
|
|
13
|
-
export { type SupabaseDatabaseConfig, type SupabaseEdgeFunctionStorageConfig as SupabaseStorageConfig, supabaseDatabase, supabaseEdgeFunctionStorage as supabaseStorage };
|
|
13
|
+
export { type SupabaseDatabaseConfig, type SupabaseEdgeFunctionStorageConfig as SupabaseStorageConfig, plugins, supabaseDatabase, supabaseEdgeFunctionStorage as supabaseStorage };
|
package/dist/edge.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as supabaseDatabase, t as SupabaseDatabaseConfig } from "./supabaseDatabase-
|
|
2
|
-
import {
|
|
1
|
+
import { i as plugins, n as supabaseDatabase, t as SupabaseDatabaseConfig } from "./supabaseDatabase-DVb3-y2x.mjs";
|
|
2
|
+
import { StorageAdapterWith } from "@hot-updater/plugin-core";
|
|
3
3
|
//#region src/supabaseEdgeFunctionStorage.d.ts
|
|
4
4
|
interface SupabaseEdgeFunctionStorageConfig {
|
|
5
5
|
supabaseUrl: string;
|
|
@@ -8,6 +8,6 @@ interface SupabaseEdgeFunctionStorageConfig {
|
|
|
8
8
|
basePath?: string;
|
|
9
9
|
signedUrlExpiresIn?: number;
|
|
10
10
|
}
|
|
11
|
-
declare const supabaseEdgeFunctionStorage: (config: SupabaseEdgeFunctionStorageConfig) =>
|
|
11
|
+
declare const supabaseEdgeFunctionStorage: (config: SupabaseEdgeFunctionStorageConfig) => StorageAdapterWith<"put" | "get" | "getDownloadUrl" | "exists" | "delete">;
|
|
12
12
|
//#endregion
|
|
13
|
-
export { type SupabaseDatabaseConfig, type SupabaseEdgeFunctionStorageConfig as SupabaseStorageConfig, supabaseDatabase, supabaseEdgeFunctionStorage as supabaseStorage };
|
|
13
|
+
export { type SupabaseDatabaseConfig, type SupabaseEdgeFunctionStorageConfig as SupabaseStorageConfig, plugins, supabaseDatabase, supabaseEdgeFunctionStorage as supabaseStorage };
|
package/dist/edge.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as supabaseDatabase, t as
|
|
1
|
+
import { n as supabaseStorage, r as supabaseDatabase, t as plugins } from "./plugins-Dj-4Pk3j.mjs";
|
|
2
2
|
//#region src/supabaseEdgeFunctionStorage.ts
|
|
3
3
|
const supabaseEdgeFunctionStorage = (config) => supabaseStorage(config);
|
|
4
4
|
//#endregion
|
|
5
|
-
export { supabaseDatabase, supabaseEdgeFunctionStorage as supabaseStorage };
|
|
5
|
+
export { plugins, supabaseDatabase, supabaseEdgeFunctionStorage as supabaseStorage };
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,144 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
const require_plugins = require("./plugins-jpIkI_T2.cjs");
|
|
3
|
+
let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
|
|
4
|
+
//#region src/supabaseSchema.ts
|
|
5
|
+
/** Every table the apply RPC may name: models, index tables, settings, and the write guard. */
|
|
6
|
+
const supabaseTableNames = (schema = _hot_updater_plugin_core.coreTarget.schema) => [
|
|
7
|
+
...schema.tables.flatMap((table) => [table.name, ...table.indexes.filter((index) => (0, _hot_updater_plugin_core.isMultiIndex)(table, index)).map((index) => `${table.name}__${index.name}`)]),
|
|
8
|
+
_hot_updater_plugin_core.SETTINGS_TABLE.name,
|
|
9
|
+
_hot_updater_plugin_core.WRITE_GUARD_TABLE.name
|
|
10
|
+
].map((name) => require_plugins.SUPABASE_TABLE_PREFIX + name);
|
|
11
|
+
/** The words the SQL core's statements use besides quoted names, numbers, and operators. */
|
|
12
|
+
const KEYWORDS = [
|
|
13
|
+
"SELECT",
|
|
14
|
+
"FROM",
|
|
15
|
+
"WHERE",
|
|
16
|
+
"AND",
|
|
17
|
+
"OR",
|
|
18
|
+
"NOT",
|
|
19
|
+
"EXISTS",
|
|
20
|
+
"INSERT",
|
|
21
|
+
"INTO",
|
|
22
|
+
"VALUES",
|
|
23
|
+
"UPDATE",
|
|
24
|
+
"SET",
|
|
25
|
+
"DELETE",
|
|
26
|
+
"ORDER",
|
|
27
|
+
"BY",
|
|
28
|
+
"ASC",
|
|
29
|
+
"DESC",
|
|
30
|
+
"LIMIT",
|
|
31
|
+
"IS",
|
|
32
|
+
"NULL",
|
|
33
|
+
"ON",
|
|
34
|
+
"CONFLICT",
|
|
35
|
+
"DO",
|
|
36
|
+
"JOIN",
|
|
37
|
+
"FOR",
|
|
38
|
+
"bigint",
|
|
39
|
+
"double",
|
|
40
|
+
"precision",
|
|
41
|
+
"boolean",
|
|
42
|
+
"jsonb",
|
|
43
|
+
"i",
|
|
44
|
+
"b"
|
|
45
|
+
];
|
|
46
|
+
const literal = (value) => `'${value.replaceAll("'", "''")}'`;
|
|
47
|
+
/**
|
|
48
|
+
* Runs a batch of the SQL core's statements in the caller's transaction and
|
|
49
|
+
* privileges (service role only), with every parameter read from one jsonb
|
|
50
|
+
* array. Only the core's statement shapes on Hot Updater's tables pass: one
|
|
51
|
+
* statement each, no literal, comment, or function call, and no word the
|
|
52
|
+
* core does not write.
|
|
53
|
+
*/
|
|
54
|
+
const applyFunction = (tables) => `CREATE OR REPLACE FUNCTION public.${require_plugins.SUPABASE_APPLY_FUNCTION}(p_statements jsonb)
|
|
55
|
+
RETURNS jsonb
|
|
56
|
+
LANGUAGE plpgsql
|
|
57
|
+
SECURITY INVOKER
|
|
58
|
+
SET search_path = public, pg_temp
|
|
59
|
+
AS $apply$
|
|
60
|
+
DECLARE
|
|
61
|
+
item jsonb;
|
|
62
|
+
statement text;
|
|
63
|
+
shape text;
|
|
64
|
+
target text;
|
|
65
|
+
found record;
|
|
66
|
+
rows jsonb;
|
|
67
|
+
changed bigint;
|
|
68
|
+
results jsonb := '[]'::jsonb;
|
|
69
|
+
BEGIN
|
|
70
|
+
FOR item IN SELECT value FROM jsonb_array_elements(p_statements) LOOP
|
|
71
|
+
statement := item->>'sql';
|
|
72
|
+
FOR target IN SELECT (regexp_matches(statement, '(?:FROM|INTO|UPDATE|JOIN) "([^"]+)"', 'g'))[1] LOOP
|
|
73
|
+
IF NOT target = ANY (ARRAY[${tables.map(literal).join(", ")}]) THEN
|
|
74
|
+
RAISE EXCEPTION '${require_plugins.SUPABASE_APPLY_FUNCTION}: % is not a Hot Updater table', target USING ERRCODE = '42501';
|
|
75
|
+
END IF;
|
|
76
|
+
END LOOP;
|
|
77
|
+
shape := regexp_replace(regexp_replace(statement, '^INSERT INTO "[^"]+" \\(', 'INSERT INTO ('), '"[^"]*"', '"', 'g');
|
|
78
|
+
IF shape !~ '^(SELECT|INSERT INTO|UPDATE|DELETE FROM) '
|
|
79
|
+
OR shape ~ '"[[:space:]]*\\('
|
|
80
|
+
OR shape ~ '[;''\\\\]|--|/\\*'
|
|
81
|
+
OR regexp_replace(shape, '\\m(${KEYWORDS.join("|")})\\M', '', 'gi') !~ '^[[:space:]"(),.*=<>+0-9$:-]*$' THEN
|
|
82
|
+
RAISE EXCEPTION '${require_plugins.SUPABASE_APPLY_FUNCTION}: statement not allowed' USING ERRCODE = '42501';
|
|
83
|
+
END IF;
|
|
84
|
+
IF statement ~ '^SELECT ' THEN
|
|
85
|
+
rows := '[]'::jsonb;
|
|
86
|
+
FOR found IN EXECUTE statement USING item->'params' LOOP
|
|
87
|
+
rows := rows || jsonb_build_array(to_jsonb(found));
|
|
88
|
+
END LOOP;
|
|
89
|
+
results := results || jsonb_build_array(jsonb_build_object('rows', rows, 'changes', 0));
|
|
90
|
+
ELSE
|
|
91
|
+
EXECUTE statement USING item->'params';
|
|
92
|
+
GET DIAGNOSTICS changed = ROW_COUNT;
|
|
93
|
+
results := results || jsonb_build_array(jsonb_build_object('rows', '[]'::jsonb, 'changes', changed));
|
|
94
|
+
END IF;
|
|
95
|
+
END LOOP;
|
|
96
|
+
RETURN results;
|
|
97
|
+
END;
|
|
98
|
+
$apply$`;
|
|
99
|
+
/**
|
|
100
|
+
* Supabase's migration: the shared SQL schema under the table prefix, the
|
|
101
|
+
* write guard, row-level security on every table (no policy, so only the
|
|
102
|
+
* service role reads or writes), the apply RPC for the service role alone,
|
|
103
|
+
* and the settings rows last. Every statement can run again, so a migration
|
|
104
|
+
* for a server's plugin tables repeats core's.
|
|
105
|
+
*/
|
|
106
|
+
const supabaseSchemaStatements = ({ schema, settings: expected } = _hot_updater_plugin_core.coreTarget) => {
|
|
107
|
+
const engine = (0, _hot_updater_plugin_core.generateEngineSql)("postgresql", schema, expected, { tablePrefix: require_plugins.SUPABASE_TABLE_PREFIX });
|
|
108
|
+
const settings = engine.slice(-Object.keys(expected).length);
|
|
109
|
+
const tables = supabaseTableNames(schema);
|
|
110
|
+
const apply = `public.${require_plugins.SUPABASE_APPLY_FUNCTION}(jsonb)`;
|
|
111
|
+
return [
|
|
112
|
+
...(0, _hot_updater_plugin_core.createTableStatements)("postgresql", [_hot_updater_plugin_core.WRITE_GUARD_TABLE], require_plugins.SUPABASE_TABLE_PREFIX),
|
|
113
|
+
...engine.slice(0, -settings.length),
|
|
114
|
+
...tables.map((table) => `ALTER TABLE "${table}" ENABLE ROW LEVEL SECURITY`),
|
|
115
|
+
applyFunction(tables),
|
|
116
|
+
`REVOKE EXECUTE ON FUNCTION ${apply} FROM PUBLIC, anon, authenticated`,
|
|
117
|
+
`GRANT EXECUTE ON FUNCTION ${apply} TO service_role`,
|
|
118
|
+
...settings
|
|
119
|
+
];
|
|
120
|
+
};
|
|
121
|
+
const supabaseSchemaSql = (target) => `-- HotUpdater.schema\n\n${supabaseSchemaStatements(target).map((statement) => `${statement};`).join("\n\n")}\n`;
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/supabaseMigration.ts
|
|
124
|
+
/**
|
|
125
|
+
* Hot Updater's database on Supabase, with the migration `hot-updater db
|
|
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
|
+
*/
|
|
130
|
+
const supabaseDatabase = (config) => ({
|
|
131
|
+
...require_plugins.supabaseDatabase(config),
|
|
132
|
+
generateSchema: ((version, _name, target = _hot_updater_plugin_core.coreTarget) => {
|
|
133
|
+
if (version !== "latest" && version !== _hot_updater_plugin_core.coreSettings["schema.core"]) throw new Error(`Invalid version ${version}`);
|
|
134
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14);
|
|
135
|
+
return {
|
|
136
|
+
code: supabaseSchemaSql(target),
|
|
137
|
+
path: `supabase/migrations/${timestamp}_hot-updater.sql`
|
|
138
|
+
};
|
|
139
|
+
})
|
|
140
|
+
});
|
|
141
|
+
//#endregion
|
|
142
|
+
exports.plugins = require_plugins.plugins;
|
|
143
|
+
exports.supabaseDatabase = supabaseDatabase;
|
|
144
|
+
exports.supabaseStorage = require_plugins.supabaseStorage;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { i as plugins, r as SupabaseServiceRoleConfig, t as SupabaseDatabaseConfig } from "./supabaseDatabase-DVb3-y2x.cjs";
|
|
2
|
+
import { StorageAdapterWith, ToolingDatabase } from "@hot-updater/plugin-core";
|
|
3
|
+
//#region src/supabaseMigration.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Hot Updater's database on Supabase, with the migration `hot-updater db
|
|
6
|
+
* generate` writes to `supabase/migrations`: core's tables, the server's
|
|
7
|
+
* plugin tables, the apply RPC that may name them, and their settings rows.
|
|
8
|
+
* `supabase db push` applies it.
|
|
9
|
+
*/
|
|
10
|
+
declare const supabaseDatabase: (config: SupabaseDatabaseConfig) => ToolingDatabase;
|
|
11
|
+
//#endregion
|
|
3
12
|
//#region src/supabaseStorage.d.ts
|
|
4
13
|
type SupabaseStorageConfig = SupabaseServiceRoleConfig & {
|
|
5
14
|
bucketName: string;
|
|
@@ -8,6 +17,6 @@ type SupabaseStorageConfig = SupabaseServiceRoleConfig & {
|
|
|
8
17
|
/** Signed download URL lifetime in seconds. @default 3600 */
|
|
9
18
|
signedUrlExpiresIn?: number;
|
|
10
19
|
};
|
|
11
|
-
declare const supabaseStorage: (config: SupabaseStorageConfig) =>
|
|
20
|
+
declare const supabaseStorage: (config: SupabaseStorageConfig) => StorageAdapterWith<"put" | "get" | "getDownloadUrl" | "exists" | "delete">;
|
|
12
21
|
//#endregion
|
|
13
|
-
export { SupabaseDatabaseConfig, SupabaseStorageConfig, supabaseDatabase, supabaseStorage };
|
|
22
|
+
export { type SupabaseDatabaseConfig, SupabaseStorageConfig, plugins, supabaseDatabase, supabaseStorage };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { i as plugins, r as SupabaseServiceRoleConfig, t as SupabaseDatabaseConfig } from "./supabaseDatabase-DVb3-y2x.mjs";
|
|
2
|
+
import { StorageAdapterWith, ToolingDatabase } from "@hot-updater/plugin-core";
|
|
3
|
+
//#region src/supabaseMigration.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Hot Updater's database on Supabase, with the migration `hot-updater db
|
|
6
|
+
* generate` writes to `supabase/migrations`: core's tables, the server's
|
|
7
|
+
* plugin tables, the apply RPC that may name them, and their settings rows.
|
|
8
|
+
* `supabase db push` applies it.
|
|
9
|
+
*/
|
|
10
|
+
declare const supabaseDatabase: (config: SupabaseDatabaseConfig) => ToolingDatabase;
|
|
11
|
+
//#endregion
|
|
3
12
|
//#region src/supabaseStorage.d.ts
|
|
4
13
|
type SupabaseStorageConfig = SupabaseServiceRoleConfig & {
|
|
5
14
|
bucketName: string;
|
|
@@ -8,6 +17,6 @@ type SupabaseStorageConfig = SupabaseServiceRoleConfig & {
|
|
|
8
17
|
/** Signed download URL lifetime in seconds. @default 3600 */
|
|
9
18
|
signedUrlExpiresIn?: number;
|
|
10
19
|
};
|
|
11
|
-
declare const supabaseStorage: (config: SupabaseStorageConfig) =>
|
|
20
|
+
declare const supabaseStorage: (config: SupabaseStorageConfig) => StorageAdapterWith<"put" | "get" | "getDownloadUrl" | "exists" | "delete">;
|
|
12
21
|
//#endregion
|
|
13
|
-
export { SupabaseDatabaseConfig, SupabaseStorageConfig, supabaseDatabase, supabaseStorage };
|
|
22
|
+
export { type SupabaseDatabaseConfig, SupabaseStorageConfig, plugins, supabaseDatabase, supabaseStorage };
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,141 @@
|
|
|
1
|
-
import { n as supabaseDatabase, t as
|
|
2
|
-
|
|
1
|
+
import { a as SUPABASE_TABLE_PREFIX, i as SUPABASE_APPLY_FUNCTION, n as supabaseStorage, r as supabaseDatabase$1, t as plugins } from "./plugins-Dj-4Pk3j.mjs";
|
|
2
|
+
import { SETTINGS_TABLE, WRITE_GUARD_TABLE, coreSettings, coreTarget, createTableStatements, generateEngineSql, isMultiIndex } from "@hot-updater/plugin-core";
|
|
3
|
+
//#region src/supabaseSchema.ts
|
|
4
|
+
/** Every table the apply RPC may name: models, index tables, settings, and the write guard. */
|
|
5
|
+
const supabaseTableNames = (schema = coreTarget.schema) => [
|
|
6
|
+
...schema.tables.flatMap((table) => [table.name, ...table.indexes.filter((index) => isMultiIndex(table, index)).map((index) => `${table.name}__${index.name}`)]),
|
|
7
|
+
SETTINGS_TABLE.name,
|
|
8
|
+
WRITE_GUARD_TABLE.name
|
|
9
|
+
].map((name) => SUPABASE_TABLE_PREFIX + name);
|
|
10
|
+
/** The words the SQL core's statements use besides quoted names, numbers, and operators. */
|
|
11
|
+
const KEYWORDS = [
|
|
12
|
+
"SELECT",
|
|
13
|
+
"FROM",
|
|
14
|
+
"WHERE",
|
|
15
|
+
"AND",
|
|
16
|
+
"OR",
|
|
17
|
+
"NOT",
|
|
18
|
+
"EXISTS",
|
|
19
|
+
"INSERT",
|
|
20
|
+
"INTO",
|
|
21
|
+
"VALUES",
|
|
22
|
+
"UPDATE",
|
|
23
|
+
"SET",
|
|
24
|
+
"DELETE",
|
|
25
|
+
"ORDER",
|
|
26
|
+
"BY",
|
|
27
|
+
"ASC",
|
|
28
|
+
"DESC",
|
|
29
|
+
"LIMIT",
|
|
30
|
+
"IS",
|
|
31
|
+
"NULL",
|
|
32
|
+
"ON",
|
|
33
|
+
"CONFLICT",
|
|
34
|
+
"DO",
|
|
35
|
+
"JOIN",
|
|
36
|
+
"FOR",
|
|
37
|
+
"bigint",
|
|
38
|
+
"double",
|
|
39
|
+
"precision",
|
|
40
|
+
"boolean",
|
|
41
|
+
"jsonb",
|
|
42
|
+
"i",
|
|
43
|
+
"b"
|
|
44
|
+
];
|
|
45
|
+
const literal = (value) => `'${value.replaceAll("'", "''")}'`;
|
|
46
|
+
/**
|
|
47
|
+
* Runs a batch of the SQL core's statements in the caller's transaction and
|
|
48
|
+
* privileges (service role only), with every parameter read from one jsonb
|
|
49
|
+
* array. Only the core's statement shapes on Hot Updater's tables pass: one
|
|
50
|
+
* statement each, no literal, comment, or function call, and no word the
|
|
51
|
+
* core does not write.
|
|
52
|
+
*/
|
|
53
|
+
const applyFunction = (tables) => `CREATE OR REPLACE FUNCTION public.${SUPABASE_APPLY_FUNCTION}(p_statements jsonb)
|
|
54
|
+
RETURNS jsonb
|
|
55
|
+
LANGUAGE plpgsql
|
|
56
|
+
SECURITY INVOKER
|
|
57
|
+
SET search_path = public, pg_temp
|
|
58
|
+
AS $apply$
|
|
59
|
+
DECLARE
|
|
60
|
+
item jsonb;
|
|
61
|
+
statement text;
|
|
62
|
+
shape text;
|
|
63
|
+
target text;
|
|
64
|
+
found record;
|
|
65
|
+
rows jsonb;
|
|
66
|
+
changed bigint;
|
|
67
|
+
results jsonb := '[]'::jsonb;
|
|
68
|
+
BEGIN
|
|
69
|
+
FOR item IN SELECT value FROM jsonb_array_elements(p_statements) LOOP
|
|
70
|
+
statement := item->>'sql';
|
|
71
|
+
FOR target IN SELECT (regexp_matches(statement, '(?:FROM|INTO|UPDATE|JOIN) "([^"]+)"', 'g'))[1] LOOP
|
|
72
|
+
IF NOT target = ANY (ARRAY[${tables.map(literal).join(", ")}]) THEN
|
|
73
|
+
RAISE EXCEPTION '${SUPABASE_APPLY_FUNCTION}: % is not a Hot Updater table', target USING ERRCODE = '42501';
|
|
74
|
+
END IF;
|
|
75
|
+
END LOOP;
|
|
76
|
+
shape := regexp_replace(regexp_replace(statement, '^INSERT INTO "[^"]+" \\(', 'INSERT INTO ('), '"[^"]*"', '"', 'g');
|
|
77
|
+
IF shape !~ '^(SELECT|INSERT INTO|UPDATE|DELETE FROM) '
|
|
78
|
+
OR shape ~ '"[[:space:]]*\\('
|
|
79
|
+
OR shape ~ '[;''\\\\]|--|/\\*'
|
|
80
|
+
OR regexp_replace(shape, '\\m(${KEYWORDS.join("|")})\\M', '', 'gi') !~ '^[[:space:]"(),.*=<>+0-9$:-]*$' THEN
|
|
81
|
+
RAISE EXCEPTION '${SUPABASE_APPLY_FUNCTION}: statement not allowed' USING ERRCODE = '42501';
|
|
82
|
+
END IF;
|
|
83
|
+
IF statement ~ '^SELECT ' THEN
|
|
84
|
+
rows := '[]'::jsonb;
|
|
85
|
+
FOR found IN EXECUTE statement USING item->'params' LOOP
|
|
86
|
+
rows := rows || jsonb_build_array(to_jsonb(found));
|
|
87
|
+
END LOOP;
|
|
88
|
+
results := results || jsonb_build_array(jsonb_build_object('rows', rows, 'changes', 0));
|
|
89
|
+
ELSE
|
|
90
|
+
EXECUTE statement USING item->'params';
|
|
91
|
+
GET DIAGNOSTICS changed = ROW_COUNT;
|
|
92
|
+
results := results || jsonb_build_array(jsonb_build_object('rows', '[]'::jsonb, 'changes', changed));
|
|
93
|
+
END IF;
|
|
94
|
+
END LOOP;
|
|
95
|
+
RETURN results;
|
|
96
|
+
END;
|
|
97
|
+
$apply$`;
|
|
98
|
+
/**
|
|
99
|
+
* Supabase's migration: the shared SQL schema under the table prefix, the
|
|
100
|
+
* write guard, row-level security on every table (no policy, so only the
|
|
101
|
+
* service role reads or writes), the apply RPC for the service role alone,
|
|
102
|
+
* and the settings rows last. Every statement can run again, so a migration
|
|
103
|
+
* for a server's plugin tables repeats core's.
|
|
104
|
+
*/
|
|
105
|
+
const supabaseSchemaStatements = ({ schema, settings: expected } = coreTarget) => {
|
|
106
|
+
const engine = generateEngineSql("postgresql", schema, expected, { tablePrefix: SUPABASE_TABLE_PREFIX });
|
|
107
|
+
const settings = engine.slice(-Object.keys(expected).length);
|
|
108
|
+
const tables = supabaseTableNames(schema);
|
|
109
|
+
const apply = `public.${SUPABASE_APPLY_FUNCTION}(jsonb)`;
|
|
110
|
+
return [
|
|
111
|
+
...createTableStatements("postgresql", [WRITE_GUARD_TABLE], SUPABASE_TABLE_PREFIX),
|
|
112
|
+
...engine.slice(0, -settings.length),
|
|
113
|
+
...tables.map((table) => `ALTER TABLE "${table}" ENABLE ROW LEVEL SECURITY`),
|
|
114
|
+
applyFunction(tables),
|
|
115
|
+
`REVOKE EXECUTE ON FUNCTION ${apply} FROM PUBLIC, anon, authenticated`,
|
|
116
|
+
`GRANT EXECUTE ON FUNCTION ${apply} TO service_role`,
|
|
117
|
+
...settings
|
|
118
|
+
];
|
|
119
|
+
};
|
|
120
|
+
const supabaseSchemaSql = (target) => `-- HotUpdater.schema\n\n${supabaseSchemaStatements(target).map((statement) => `${statement};`).join("\n\n")}\n`;
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/supabaseMigration.ts
|
|
123
|
+
/**
|
|
124
|
+
* Hot Updater's database on Supabase, with the migration `hot-updater db
|
|
125
|
+
* generate` writes to `supabase/migrations`: core's tables, the server's
|
|
126
|
+
* plugin tables, the apply RPC that may name them, and their settings rows.
|
|
127
|
+
* `supabase db push` applies it.
|
|
128
|
+
*/
|
|
129
|
+
const supabaseDatabase = (config) => ({
|
|
130
|
+
...supabaseDatabase$1(config),
|
|
131
|
+
generateSchema: ((version, _name, target = coreTarget) => {
|
|
132
|
+
if (version !== "latest" && version !== coreSettings["schema.core"]) throw new Error(`Invalid version ${version}`);
|
|
133
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14);
|
|
134
|
+
return {
|
|
135
|
+
code: supabaseSchemaSql(target),
|
|
136
|
+
path: `supabase/migrations/${timestamp}_hot-updater.sql`
|
|
137
|
+
};
|
|
138
|
+
})
|
|
139
|
+
});
|
|
140
|
+
//#endregion
|
|
141
|
+
export { plugins, supabaseDatabase, supabaseStorage };
|