@hot-updater/supabase 1.2.0-rc.1 → 1.2.0-rc.11
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.d.cts +1 -1
- package/dist/edge.d.mts +1 -1
- package/dist/edge.mjs +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/init/index.cjs +52 -20
- package/dist/init/index.d.cts +1 -0
- package/dist/init/index.d.mts +1 -0
- package/dist/init/index.mjs +52 -20
- package/dist/{supabaseStorage-D4jcGa-l.mjs → supabaseStorage-B6IM8UX2.mjs} +106 -12
- package/dist/{supabaseStorage-BryXQ72m.d.cts → supabaseStorage-C9W-69OP.d.cts} +8 -3
- package/dist/{supabaseStorage-BryXQ72m.d.mts → supabaseStorage-C9W-69OP.d.mts} +8 -3
- package/dist/{supabaseStorage-MqoRcaii.cjs → supabaseStorage-DUa49vGh.cjs} +106 -12
- package/package.json +8 -8
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-DUa49vGh.cjs");
|
|
3
3
|
exports.supabaseDatabase = require_supabaseStorage.supabaseDatabase;
|
|
4
4
|
exports.supabaseStorage = require_supabaseStorage.supabaseStorage;
|
package/dist/edge.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as supabaseDatabase, n as supabaseStorage, r as SupabaseDatabaseConfig, t as SupabaseStorageConfig } from "./supabaseStorage-
|
|
1
|
+
import { i as supabaseDatabase, n as supabaseStorage, r as SupabaseDatabaseConfig, t as SupabaseStorageConfig } from "./supabaseStorage-C9W-69OP.cjs";
|
|
2
2
|
export { type SupabaseDatabaseConfig, type SupabaseStorageConfig, supabaseDatabase, supabaseStorage };
|
package/dist/edge.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as supabaseDatabase, n as supabaseStorage, r as SupabaseDatabaseConfig, t as SupabaseStorageConfig } from "./supabaseStorage-
|
|
1
|
+
import { i as supabaseDatabase, n as supabaseStorage, r as SupabaseDatabaseConfig, t as SupabaseStorageConfig } from "./supabaseStorage-C9W-69OP.mjs";
|
|
2
2
|
export { type SupabaseDatabaseConfig, type SupabaseStorageConfig, supabaseDatabase, 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-B6IM8UX2.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-DUa49vGh.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. */
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as supabaseStorage, r as SupabaseDatabaseConfig, t as SupabaseStorageConfig } from "./supabaseStorage-
|
|
1
|
+
import { n as supabaseStorage, r as SupabaseDatabaseConfig, t as SupabaseStorageConfig } from "./supabaseStorage-C9W-69OP.cjs";
|
|
2
2
|
import { ToolingDatabase } from "@hot-updater/plugin-core";
|
|
3
3
|
//#region src/supabaseMigration.d.ts
|
|
4
4
|
/**
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as supabaseStorage, r as SupabaseDatabaseConfig, t as SupabaseStorageConfig } from "./supabaseStorage-
|
|
1
|
+
import { n as supabaseStorage, r as SupabaseDatabaseConfig, t as SupabaseStorageConfig } from "./supabaseStorage-C9W-69OP.mjs";
|
|
2
2
|
import { ToolingDatabase } from "@hot-updater/plugin-core";
|
|
3
3
|
//#region src/supabaseMigration.d.ts
|
|
4
4
|
/**
|
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-B6IM8UX2.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. */
|
package/dist/init/index.cjs
CHANGED
|
@@ -6333,9 +6333,11 @@ createExeca(mapScriptAsync, {}, deepScriptOptions, setScriptSync);
|
|
|
6333
6333
|
const { sendMessage, getOneMessage, getEachMessage, getCancelSignal } = getIpcExport();
|
|
6334
6334
|
//#endregion
|
|
6335
6335
|
//#region src/supabaseConfig.ts
|
|
6336
|
-
const resolveSupabaseServiceRoleKey = (config) => {
|
|
6337
|
-
|
|
6338
|
-
|
|
6336
|
+
const resolveSupabaseServiceRoleKey = async (config) => {
|
|
6337
|
+
const { supabaseServiceRoleKey } = config;
|
|
6338
|
+
const key = typeof supabaseServiceRoleKey === "function" ? await supabaseServiceRoleKey() : supabaseServiceRoleKey;
|
|
6339
|
+
if (!key) throw new Error("Supabase service role key is required. Set supabaseServiceRoleKey.");
|
|
6340
|
+
return key;
|
|
6339
6341
|
};
|
|
6340
6342
|
//#endregion
|
|
6341
6343
|
//#region src/supabaseInfrastructureNames.ts
|
|
@@ -6375,9 +6377,16 @@ const withCode = (error) => Object.assign(new Error(error.message, { cause: erro
|
|
|
6375
6377
|
*/
|
|
6376
6378
|
const supabaseExecutor = (client) => {
|
|
6377
6379
|
let created;
|
|
6378
|
-
const clientOf = () =>
|
|
6380
|
+
const clientOf = async () => {
|
|
6381
|
+
if (typeof client !== "function") return client;
|
|
6382
|
+
created ??= Promise.resolve().then(client).catch((error) => {
|
|
6383
|
+
created = void 0;
|
|
6384
|
+
throw error;
|
|
6385
|
+
});
|
|
6386
|
+
return created;
|
|
6387
|
+
};
|
|
6379
6388
|
const apply = async (statements) => {
|
|
6380
|
-
const { data, error } = await clientOf().rpc(SUPABASE_APPLY_FUNCTION, { p_statements: statements.map(toApplyStatement) });
|
|
6389
|
+
const { data, error } = await (await clientOf()).rpc(SUPABASE_APPLY_FUNCTION, { p_statements: statements.map(toApplyStatement) });
|
|
6381
6390
|
if (error) throw withCode(error);
|
|
6382
6391
|
return data;
|
|
6383
6392
|
};
|
|
@@ -6400,7 +6409,7 @@ const supabaseExecutor = (client) => {
|
|
|
6400
6409
|
*/
|
|
6401
6410
|
const supabaseDatabase = (config) => {
|
|
6402
6411
|
const { migrations: _migrations, ...adapter } = (0, _hot_updater_plugin_core.createSqlAdapter)({
|
|
6403
|
-
executor: supabaseExecutor(() => (0, _supabase_supabase_js.createClient)(config.supabaseUrl, resolveSupabaseServiceRoleKey(config))),
|
|
6412
|
+
executor: supabaseExecutor(async () => (0, _supabase_supabase_js.createClient)(config.supabaseUrl, await resolveSupabaseServiceRoleKey(config))),
|
|
6404
6413
|
tablePrefix: SUPABASE_TABLE_PREFIX
|
|
6405
6414
|
});
|
|
6406
6415
|
return (0, _hot_updater_plugin_core.createEngineDatabase)({
|
|
@@ -6410,27 +6419,39 @@ const supabaseDatabase = (config) => {
|
|
|
6410
6419
|
};
|
|
6411
6420
|
//#endregion
|
|
6412
6421
|
//#region iac/configTemplate.ts
|
|
6413
|
-
|
|
6414
|
-
|
|
6422
|
+
/**
|
|
6423
|
+
* The config init writes. With the `resources` init set up, it names them,
|
|
6424
|
+
* and reads the service role key with `secret()`, from the environment and
|
|
6425
|
+
* `.env.hotupdater`, then the config's `secrets` adapter. Without them, as
|
|
6426
|
+
* the agent scaffold writes it before the agent creates the project, it
|
|
6427
|
+
* reads the URL and bucket from the environment, where the agent records
|
|
6428
|
+
* them, and the key with `secret()` alike.
|
|
6429
|
+
*/
|
|
6430
|
+
const getConfigScaffold = (build, resources) => {
|
|
6431
|
+
const supabaseUrl = resources === void 0 ? "process.env.HOT_UPDATER_SUPABASE_URL!" : JSON.stringify(resources.supabaseUrl);
|
|
6432
|
+
const serviceRoleKey = "secret(\"HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY\")";
|
|
6433
|
+
const storageConfig = {
|
|
6415
6434
|
imports: [{
|
|
6416
6435
|
pkg: "@hot-updater/supabase",
|
|
6417
6436
|
named: ["supabaseStorage"]
|
|
6418
6437
|
}],
|
|
6419
6438
|
configString: `supabaseStorage({
|
|
6420
|
-
supabaseUrl:
|
|
6421
|
-
supabaseServiceRoleKey:
|
|
6422
|
-
bucketName: process.env.HOT_UPDATER_SUPABASE_BUCKET_NAME
|
|
6439
|
+
supabaseUrl: ${supabaseUrl},
|
|
6440
|
+
supabaseServiceRoleKey: ${serviceRoleKey},
|
|
6441
|
+
bucketName: ${resources === void 0 ? "process.env.HOT_UPDATER_SUPABASE_BUCKET_NAME!" : JSON.stringify(resources.bucketName)},
|
|
6423
6442
|
})`
|
|
6424
|
-
}
|
|
6443
|
+
};
|
|
6444
|
+
const databaseConfig = {
|
|
6425
6445
|
imports: [{
|
|
6426
6446
|
pkg: "@hot-updater/supabase",
|
|
6427
6447
|
named: ["supabaseDatabase"]
|
|
6428
6448
|
}],
|
|
6429
6449
|
configString: `supabaseDatabase({
|
|
6430
|
-
supabaseUrl:
|
|
6431
|
-
supabaseServiceRoleKey:
|
|
6450
|
+
supabaseUrl: ${supabaseUrl},
|
|
6451
|
+
supabaseServiceRoleKey: ${serviceRoleKey},
|
|
6432
6452
|
})`
|
|
6433
|
-
}
|
|
6453
|
+
};
|
|
6454
|
+
const builder = new _hot_updater_cli_tools.ConfigBuilder().setBuildType(build).setStorage(storageConfig).setDatabase(databaseConfig).setPlugins({
|
|
6434
6455
|
imports: [{
|
|
6435
6456
|
pkg: "hot-updater/plugins",
|
|
6436
6457
|
named: [
|
|
@@ -6440,7 +6461,12 @@ const getConfigScaffold = (build) => {
|
|
|
6440
6461
|
]
|
|
6441
6462
|
}],
|
|
6442
6463
|
configString: "[apiKeys(), insights(), remoteConfig()]"
|
|
6443
|
-
})
|
|
6464
|
+
});
|
|
6465
|
+
builder.addImport({
|
|
6466
|
+
pkg: "hot-updater",
|
|
6467
|
+
named: ["secret"]
|
|
6468
|
+
});
|
|
6469
|
+
return (0, _hot_updater_cli_tools.createHotUpdaterConfigScaffoldFromBuilder)(builder);
|
|
6444
6470
|
};
|
|
6445
6471
|
//#endregion
|
|
6446
6472
|
//#region iac/init/index.ts
|
|
@@ -6546,7 +6572,8 @@ const initProvider = {
|
|
|
6546
6572
|
type: "password"
|
|
6547
6573
|
}
|
|
6548
6574
|
}
|
|
6549
|
-
}
|
|
6575
|
+
},
|
|
6576
|
+
secrets: ["HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY"]
|
|
6550
6577
|
};
|
|
6551
6578
|
//#endregion
|
|
6552
6579
|
//#region iac/supabaseApi.ts
|
|
@@ -7515,9 +7542,9 @@ const withSupabaseCliMetadataCleanup = async (cwd, operation) => {
|
|
|
7515
7542
|
});
|
|
7516
7543
|
}
|
|
7517
7544
|
};
|
|
7518
|
-
const runInitWithoutCliMetadata = async ({ build, envFile }) => {
|
|
7545
|
+
const runInitWithoutCliMetadata = async ({ build, envFile, secretsEnv }) => {
|
|
7519
7546
|
const nonInteractive = envFile !== void 0;
|
|
7520
|
-
const initEnvSources = await (0, _hot_updater_cli_tools.readHotUpdaterInitEnv)(process.cwd(), envFile);
|
|
7547
|
+
const initEnvSources = await (0, _hot_updater_cli_tools.readHotUpdaterInitEnv)(process.cwd(), envFile, secretsEnv);
|
|
7521
7548
|
const { inputEnv, managedEnv } = initEnvSources;
|
|
7522
7549
|
const initInputEnv = (0, _hot_updater_cli_tools.getHotUpdaterInitInputEnv)(initEnvSources, nonInteractive);
|
|
7523
7550
|
const savedInputs = resolveSupabaseInitInputs(initInputEnv, {
|
|
@@ -7596,6 +7623,7 @@ const runInitWithoutCliMetadata = async ({ build, envFile }) => {
|
|
|
7596
7623
|
const { tmpDir, removeTmpDir } = await (0, _hot_updater_cli_tools.copyDirToTmp)(scaffoldLibPath, "supabase");
|
|
7597
7624
|
let credential;
|
|
7598
7625
|
let clientPlugins = [];
|
|
7626
|
+
let resources;
|
|
7599
7627
|
try {
|
|
7600
7628
|
await stageEdgeFunction({
|
|
7601
7629
|
bucketName: bucketSelection.name,
|
|
@@ -7637,6 +7665,10 @@ const runInitWithoutCliMetadata = async ({ build, envFile }) => {
|
|
|
7637
7665
|
[initProvider.inputs.bucketName.envKey]: bucket.name,
|
|
7638
7666
|
HOT_UPDATER_SUPABASE_URL: `https://${project.id}.supabase.co`
|
|
7639
7667
|
});
|
|
7668
|
+
resources = {
|
|
7669
|
+
supabaseUrl: `https://${project.id}.supabase.co`,
|
|
7670
|
+
bucketName: bucket.name
|
|
7671
|
+
};
|
|
7640
7672
|
await linkSupabase(tmpDir, {
|
|
7641
7673
|
accessToken,
|
|
7642
7674
|
projectId: project.id,
|
|
@@ -7674,7 +7706,7 @@ const runInitWithoutCliMetadata = async ({ build, envFile }) => {
|
|
|
7674
7706
|
await removeTmpDir();
|
|
7675
7707
|
}
|
|
7676
7708
|
_hot_updater_cli_tools.p.log.success("Generated '.env.hotupdater' file with Supabase settings.");
|
|
7677
|
-
await (0, _hot_updater_cli_tools.writeHotUpdaterFiles)(getConfigScaffold(build), {
|
|
7709
|
+
await (0, _hot_updater_cli_tools.writeHotUpdaterFiles)(getConfigScaffold(build, resources), {
|
|
7678
7710
|
cwd: process.cwd(),
|
|
7679
7711
|
settings: "Supabase"
|
|
7680
7712
|
});
|
package/dist/init/index.d.cts
CHANGED
package/dist/init/index.d.mts
CHANGED
package/dist/init/index.mjs
CHANGED
|
@@ -6328,9 +6328,11 @@ createExeca(mapScriptAsync, {}, deepScriptOptions, setScriptSync);
|
|
|
6328
6328
|
const { sendMessage, getOneMessage, getEachMessage, getCancelSignal } = getIpcExport();
|
|
6329
6329
|
//#endregion
|
|
6330
6330
|
//#region src/supabaseConfig.ts
|
|
6331
|
-
const resolveSupabaseServiceRoleKey = (config) => {
|
|
6332
|
-
|
|
6333
|
-
|
|
6331
|
+
const resolveSupabaseServiceRoleKey = async (config) => {
|
|
6332
|
+
const { supabaseServiceRoleKey } = config;
|
|
6333
|
+
const key = typeof supabaseServiceRoleKey === "function" ? await supabaseServiceRoleKey() : supabaseServiceRoleKey;
|
|
6334
|
+
if (!key) throw new Error("Supabase service role key is required. Set supabaseServiceRoleKey.");
|
|
6335
|
+
return key;
|
|
6334
6336
|
};
|
|
6335
6337
|
//#endregion
|
|
6336
6338
|
//#region src/supabaseInfrastructureNames.ts
|
|
@@ -6370,9 +6372,16 @@ const withCode = (error) => Object.assign(new Error(error.message, { cause: erro
|
|
|
6370
6372
|
*/
|
|
6371
6373
|
const supabaseExecutor = (client) => {
|
|
6372
6374
|
let created;
|
|
6373
|
-
const clientOf = () =>
|
|
6375
|
+
const clientOf = async () => {
|
|
6376
|
+
if (typeof client !== "function") return client;
|
|
6377
|
+
created ??= Promise.resolve().then(client).catch((error) => {
|
|
6378
|
+
created = void 0;
|
|
6379
|
+
throw error;
|
|
6380
|
+
});
|
|
6381
|
+
return created;
|
|
6382
|
+
};
|
|
6374
6383
|
const apply = async (statements) => {
|
|
6375
|
-
const { data, error } = await clientOf().rpc(SUPABASE_APPLY_FUNCTION, { p_statements: statements.map(toApplyStatement) });
|
|
6384
|
+
const { data, error } = await (await clientOf()).rpc(SUPABASE_APPLY_FUNCTION, { p_statements: statements.map(toApplyStatement) });
|
|
6376
6385
|
if (error) throw withCode(error);
|
|
6377
6386
|
return data;
|
|
6378
6387
|
};
|
|
@@ -6395,7 +6404,7 @@ const supabaseExecutor = (client) => {
|
|
|
6395
6404
|
*/
|
|
6396
6405
|
const supabaseDatabase = (config) => {
|
|
6397
6406
|
const { migrations: _migrations, ...adapter } = createSqlAdapter({
|
|
6398
|
-
executor: supabaseExecutor(() => createClient(config.supabaseUrl, resolveSupabaseServiceRoleKey(config))),
|
|
6407
|
+
executor: supabaseExecutor(async () => createClient(config.supabaseUrl, await resolveSupabaseServiceRoleKey(config))),
|
|
6399
6408
|
tablePrefix: SUPABASE_TABLE_PREFIX
|
|
6400
6409
|
});
|
|
6401
6410
|
return createEngineDatabase({
|
|
@@ -6405,27 +6414,39 @@ const supabaseDatabase = (config) => {
|
|
|
6405
6414
|
};
|
|
6406
6415
|
//#endregion
|
|
6407
6416
|
//#region iac/configTemplate.ts
|
|
6408
|
-
|
|
6409
|
-
|
|
6417
|
+
/**
|
|
6418
|
+
* The config init writes. With the `resources` init set up, it names them,
|
|
6419
|
+
* and reads the service role key with `secret()`, from the environment and
|
|
6420
|
+
* `.env.hotupdater`, then the config's `secrets` adapter. Without them, as
|
|
6421
|
+
* the agent scaffold writes it before the agent creates the project, it
|
|
6422
|
+
* reads the URL and bucket from the environment, where the agent records
|
|
6423
|
+
* them, and the key with `secret()` alike.
|
|
6424
|
+
*/
|
|
6425
|
+
const getConfigScaffold = (build, resources) => {
|
|
6426
|
+
const supabaseUrl = resources === void 0 ? "process.env.HOT_UPDATER_SUPABASE_URL!" : JSON.stringify(resources.supabaseUrl);
|
|
6427
|
+
const serviceRoleKey = "secret(\"HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY\")";
|
|
6428
|
+
const storageConfig = {
|
|
6410
6429
|
imports: [{
|
|
6411
6430
|
pkg: "@hot-updater/supabase",
|
|
6412
6431
|
named: ["supabaseStorage"]
|
|
6413
6432
|
}],
|
|
6414
6433
|
configString: `supabaseStorage({
|
|
6415
|
-
supabaseUrl:
|
|
6416
|
-
supabaseServiceRoleKey:
|
|
6417
|
-
bucketName: process.env.HOT_UPDATER_SUPABASE_BUCKET_NAME
|
|
6434
|
+
supabaseUrl: ${supabaseUrl},
|
|
6435
|
+
supabaseServiceRoleKey: ${serviceRoleKey},
|
|
6436
|
+
bucketName: ${resources === void 0 ? "process.env.HOT_UPDATER_SUPABASE_BUCKET_NAME!" : JSON.stringify(resources.bucketName)},
|
|
6418
6437
|
})`
|
|
6419
|
-
}
|
|
6438
|
+
};
|
|
6439
|
+
const databaseConfig = {
|
|
6420
6440
|
imports: [{
|
|
6421
6441
|
pkg: "@hot-updater/supabase",
|
|
6422
6442
|
named: ["supabaseDatabase"]
|
|
6423
6443
|
}],
|
|
6424
6444
|
configString: `supabaseDatabase({
|
|
6425
|
-
supabaseUrl:
|
|
6426
|
-
supabaseServiceRoleKey:
|
|
6445
|
+
supabaseUrl: ${supabaseUrl},
|
|
6446
|
+
supabaseServiceRoleKey: ${serviceRoleKey},
|
|
6427
6447
|
})`
|
|
6428
|
-
}
|
|
6448
|
+
};
|
|
6449
|
+
const builder = new ConfigBuilder().setBuildType(build).setStorage(storageConfig).setDatabase(databaseConfig).setPlugins({
|
|
6429
6450
|
imports: [{
|
|
6430
6451
|
pkg: "hot-updater/plugins",
|
|
6431
6452
|
named: [
|
|
@@ -6435,7 +6456,12 @@ const getConfigScaffold = (build) => {
|
|
|
6435
6456
|
]
|
|
6436
6457
|
}],
|
|
6437
6458
|
configString: "[apiKeys(), insights(), remoteConfig()]"
|
|
6438
|
-
})
|
|
6459
|
+
});
|
|
6460
|
+
builder.addImport({
|
|
6461
|
+
pkg: "hot-updater",
|
|
6462
|
+
named: ["secret"]
|
|
6463
|
+
});
|
|
6464
|
+
return createHotUpdaterConfigScaffoldFromBuilder(builder);
|
|
6439
6465
|
};
|
|
6440
6466
|
//#endregion
|
|
6441
6467
|
//#region iac/init/index.ts
|
|
@@ -6541,7 +6567,8 @@ const initProvider = {
|
|
|
6541
6567
|
type: "password"
|
|
6542
6568
|
}
|
|
6543
6569
|
}
|
|
6544
|
-
}
|
|
6570
|
+
},
|
|
6571
|
+
secrets: ["HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY"]
|
|
6545
6572
|
};
|
|
6546
6573
|
//#endregion
|
|
6547
6574
|
//#region iac/supabaseApi.ts
|
|
@@ -7510,9 +7537,9 @@ const withSupabaseCliMetadataCleanup = async (cwd, operation) => {
|
|
|
7510
7537
|
});
|
|
7511
7538
|
}
|
|
7512
7539
|
};
|
|
7513
|
-
const runInitWithoutCliMetadata = async ({ build, envFile }) => {
|
|
7540
|
+
const runInitWithoutCliMetadata = async ({ build, envFile, secretsEnv }) => {
|
|
7514
7541
|
const nonInteractive = envFile !== void 0;
|
|
7515
|
-
const initEnvSources = await readHotUpdaterInitEnv(process.cwd(), envFile);
|
|
7542
|
+
const initEnvSources = await readHotUpdaterInitEnv(process.cwd(), envFile, secretsEnv);
|
|
7516
7543
|
const { inputEnv, managedEnv } = initEnvSources;
|
|
7517
7544
|
const initInputEnv = getHotUpdaterInitInputEnv(initEnvSources, nonInteractive);
|
|
7518
7545
|
const savedInputs = resolveSupabaseInitInputs(initInputEnv, {
|
|
@@ -7591,6 +7618,7 @@ const runInitWithoutCliMetadata = async ({ build, envFile }) => {
|
|
|
7591
7618
|
const { tmpDir, removeTmpDir } = await copyDirToTmp(scaffoldLibPath, "supabase");
|
|
7592
7619
|
let credential;
|
|
7593
7620
|
let clientPlugins = [];
|
|
7621
|
+
let resources;
|
|
7594
7622
|
try {
|
|
7595
7623
|
await stageEdgeFunction({
|
|
7596
7624
|
bucketName: bucketSelection.name,
|
|
@@ -7632,6 +7660,10 @@ const runInitWithoutCliMetadata = async ({ build, envFile }) => {
|
|
|
7632
7660
|
[initProvider.inputs.bucketName.envKey]: bucket.name,
|
|
7633
7661
|
HOT_UPDATER_SUPABASE_URL: `https://${project.id}.supabase.co`
|
|
7634
7662
|
});
|
|
7663
|
+
resources = {
|
|
7664
|
+
supabaseUrl: `https://${project.id}.supabase.co`,
|
|
7665
|
+
bucketName: bucket.name
|
|
7666
|
+
};
|
|
7635
7667
|
await linkSupabase(tmpDir, {
|
|
7636
7668
|
accessToken,
|
|
7637
7669
|
projectId: project.id,
|
|
@@ -7669,7 +7701,7 @@ const runInitWithoutCliMetadata = async ({ build, envFile }) => {
|
|
|
7669
7701
|
await removeTmpDir();
|
|
7670
7702
|
}
|
|
7671
7703
|
p.log.success("Generated '.env.hotupdater' file with Supabase settings.");
|
|
7672
|
-
await writeHotUpdaterFiles(getConfigScaffold(build), {
|
|
7704
|
+
await writeHotUpdaterFiles(getConfigScaffold(build, resources), {
|
|
7673
7705
|
cwd: process.cwd(),
|
|
7674
7706
|
settings: "Supabase"
|
|
7675
7707
|
});
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { SETTINGS_TABLE, createEngineDatabase, createSqlAdapter, createStorageAdapter, createStorageKeyBuilder, createStorageUri, parseStorageUri } from "@hot-updater/plugin-core";
|
|
2
2
|
import { createClient } from "@supabase/supabase-js";
|
|
3
3
|
//#region src/supabaseConfig.ts
|
|
4
|
-
const resolveSupabaseServiceRoleKey = (config) => {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
const resolveSupabaseServiceRoleKey = async (config) => {
|
|
5
|
+
const { supabaseServiceRoleKey } = config;
|
|
6
|
+
const key = typeof supabaseServiceRoleKey === "function" ? await supabaseServiceRoleKey() : supabaseServiceRoleKey;
|
|
7
|
+
if (!key) throw new Error("Supabase service role key is required. Set supabaseServiceRoleKey.");
|
|
8
|
+
return key;
|
|
7
9
|
};
|
|
8
10
|
//#endregion
|
|
9
11
|
//#region src/supabaseInfrastructureNames.ts
|
|
@@ -42,9 +44,16 @@ const withCode = (error) => Object.assign(new Error(error.message, { cause: erro
|
|
|
42
44
|
*/
|
|
43
45
|
const supabaseExecutor = (client) => {
|
|
44
46
|
let created;
|
|
45
|
-
const clientOf = () =>
|
|
47
|
+
const clientOf = async () => {
|
|
48
|
+
if (typeof client !== "function") return client;
|
|
49
|
+
created ??= Promise.resolve().then(client).catch((error) => {
|
|
50
|
+
created = void 0;
|
|
51
|
+
throw error;
|
|
52
|
+
});
|
|
53
|
+
return created;
|
|
54
|
+
};
|
|
46
55
|
const apply = async (statements) => {
|
|
47
|
-
const { data, error } = await clientOf().rpc(SUPABASE_APPLY_FUNCTION, { p_statements: statements.map(toApplyStatement) });
|
|
56
|
+
const { data, error } = await (await clientOf()).rpc(SUPABASE_APPLY_FUNCTION, { p_statements: statements.map(toApplyStatement) });
|
|
48
57
|
if (error) throw withCode(error);
|
|
49
58
|
return data;
|
|
50
59
|
};
|
|
@@ -67,7 +76,7 @@ const supabaseExecutor = (client) => {
|
|
|
67
76
|
*/
|
|
68
77
|
const supabaseDatabase = (config) => {
|
|
69
78
|
const { migrations: _migrations, ...adapter } = createSqlAdapter({
|
|
70
|
-
executor: supabaseExecutor(() => createClient(config.supabaseUrl, resolveSupabaseServiceRoleKey(config))),
|
|
79
|
+
executor: supabaseExecutor(async () => createClient(config.supabaseUrl, await resolveSupabaseServiceRoleKey(config))),
|
|
71
80
|
tablePrefix: SUPABASE_TABLE_PREFIX
|
|
72
81
|
});
|
|
73
82
|
return createEngineDatabase({
|
|
@@ -157,7 +166,15 @@ const isMissingObjectError = async (error) => {
|
|
|
157
166
|
};
|
|
158
167
|
/** A character Storage rejects in an object name, `?`, or `!`. */
|
|
159
168
|
const ESCAPED_CHARACTER = /[^A-Za-z0-9_/.*'() &$=@;:+,-]/gu;
|
|
169
|
+
const ESCAPE_SEQUENCE = /!([0-9A-F]{2})/gu;
|
|
160
170
|
const utf8 = new TextEncoder();
|
|
171
|
+
const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
|
|
172
|
+
/** Objects Storage lists a page at a time. */
|
|
173
|
+
const LIST_PAGE_SIZE = 1e3;
|
|
174
|
+
/** Names Storage removes in one request. */
|
|
175
|
+
const DELETE_BATCH_SIZE = 1e3;
|
|
176
|
+
/** The object the Supabase dashboard keeps in a folder it creates. */
|
|
177
|
+
const EMPTY_FOLDER_PLACEHOLDER = ".emptyFolderPlaceholder";
|
|
161
178
|
/**
|
|
162
179
|
* The object name Storage keeps a key under. Storage accepts names of ASCII
|
|
163
180
|
* letters, digits and `_/!.*'() &$=@;:+,?-` only, and supabase-js puts a name
|
|
@@ -168,12 +185,44 @@ const utf8 = new TextEncoder();
|
|
|
168
185
|
* Deploy's keys, such as `<bundleId>/manifest.json`, are stored as they are.
|
|
169
186
|
*/
|
|
170
187
|
const toObjectName = (key) => key.replace(ESCAPED_CHARACTER, (character) => Array.from(utf8.encode(character), (byte) => `!${byte.toString(16).toUpperCase().padStart(2, "0")}`).join(""));
|
|
188
|
+
/** The key `toObjectName` stored under `name`, or null for a name it never makes. */
|
|
189
|
+
const fromObjectName = (name) => {
|
|
190
|
+
const bytes = [];
|
|
191
|
+
const parts = [];
|
|
192
|
+
const flush = () => {
|
|
193
|
+
if (bytes.length === 0) return true;
|
|
194
|
+
try {
|
|
195
|
+
parts.push(utf8Decoder.decode(new Uint8Array(bytes)));
|
|
196
|
+
} catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
bytes.length = 0;
|
|
200
|
+
return true;
|
|
201
|
+
};
|
|
202
|
+
let index = 0;
|
|
203
|
+
for (const match of name.matchAll(ESCAPE_SEQUENCE)) {
|
|
204
|
+
if (match.index !== index) {
|
|
205
|
+
if (!flush()) return null;
|
|
206
|
+
parts.push(name.slice(index, match.index));
|
|
207
|
+
}
|
|
208
|
+
bytes.push(Number.parseInt(match[1], 16));
|
|
209
|
+
index = match.index + match[0].length;
|
|
210
|
+
}
|
|
211
|
+
if (!flush()) return null;
|
|
212
|
+
parts.push(name.slice(index));
|
|
213
|
+
const key = parts.join("");
|
|
214
|
+
return toObjectName(key) === name ? key : null;
|
|
215
|
+
};
|
|
171
216
|
const supabaseStorage = (config) => {
|
|
172
217
|
let client;
|
|
173
|
-
const bucket = () => (client ??= createClient(config.supabaseUrl,
|
|
218
|
+
const bucket = async () => (await (client ??= resolveSupabaseServiceRoleKey(config).then((key) => createClient(config.supabaseUrl, key), (error) => {
|
|
219
|
+
client = void 0;
|
|
220
|
+
throw error;
|
|
221
|
+
}))).storage.from(config.bucketName);
|
|
174
222
|
const getStorageKey = createStorageKeyBuilder(config.basePath);
|
|
223
|
+
const basePrefix = getStorageKey() ? `${getStorageKey()}/` : "";
|
|
175
224
|
const resolveSignedUrl = createSupabaseSignedUrlBatcher({
|
|
176
|
-
createSignedUrls: (_bucketName, keys, expiresIn) => bucket().createSignedUrls(keys, expiresIn),
|
|
225
|
+
createSignedUrls: async (_bucketName, keys, expiresIn) => (await bucket()).createSignedUrls(keys, expiresIn),
|
|
177
226
|
expiresIn: config.signedUrlExpiresIn ?? 3600,
|
|
178
227
|
formatObjectPath: (_bucketName, key) => key
|
|
179
228
|
});
|
|
@@ -187,7 +236,7 @@ const supabaseStorage = (config) => {
|
|
|
187
236
|
protocol: "supabase-storage",
|
|
188
237
|
async put({ key, body, contentLength, contentType }) {
|
|
189
238
|
const storageKey = getStorageKey(key);
|
|
190
|
-
const { error } = await bucket().upload(toObjectName(storageKey), body, {
|
|
239
|
+
const { error } = await (await bucket()).upload(toObjectName(storageKey), body, {
|
|
191
240
|
contentType,
|
|
192
241
|
cacheControl: "max-age=31536000",
|
|
193
242
|
upsert: true,
|
|
@@ -203,7 +252,7 @@ const supabaseStorage = (config) => {
|
|
|
203
252
|
},
|
|
204
253
|
async get({ storageUri }) {
|
|
205
254
|
const { key } = parseAndValidate(storageUri);
|
|
206
|
-
const { data, error } = await bucket().download(toObjectName(key));
|
|
255
|
+
const { data, error } = await (await bucket()).download(toObjectName(key));
|
|
207
256
|
if (error) {
|
|
208
257
|
if (await isMissingObjectError(error)) return { response: null };
|
|
209
258
|
throw new Error(`Failed to download storage object: ${error.message}`);
|
|
@@ -216,7 +265,7 @@ const supabaseStorage = (config) => {
|
|
|
216
265
|
},
|
|
217
266
|
async exists({ storageUri }) {
|
|
218
267
|
const { key } = parseAndValidate(storageUri);
|
|
219
|
-
const { data, error } = await bucket().exists(toObjectName(key));
|
|
268
|
+
const { data, error } = await (await bucket()).exists(toObjectName(key));
|
|
220
269
|
if (error) {
|
|
221
270
|
if (isMissingExistsError(error)) return { exists: false };
|
|
222
271
|
throw error;
|
|
@@ -225,9 +274,54 @@ const supabaseStorage = (config) => {
|
|
|
225
274
|
},
|
|
226
275
|
async delete({ storageUri }) {
|
|
227
276
|
const { key } = parseAndValidate(storageUri);
|
|
228
|
-
const { error } = await bucket().remove([toObjectName(key)]);
|
|
277
|
+
const { error } = await (await bucket()).remove([toObjectName(key)]);
|
|
229
278
|
if (error && !isNotFoundError(error)) throw new Error(`Failed to delete storage object: ${error.message}`);
|
|
230
279
|
return { deleted: true };
|
|
280
|
+
},
|
|
281
|
+
async listObjects(prefix) {
|
|
282
|
+
const objects = [];
|
|
283
|
+
const folder = toObjectName(getStorageKey(prefix ?? ""));
|
|
284
|
+
let cursor;
|
|
285
|
+
do {
|
|
286
|
+
const { data, error } = await (await bucket()).listV2({
|
|
287
|
+
prefix: folder ? `${folder}/` : "",
|
|
288
|
+
limit: LIST_PAGE_SIZE,
|
|
289
|
+
...cursor === void 0 ? {} : { cursor }
|
|
290
|
+
});
|
|
291
|
+
if (error) throw new Error(`Failed to list storage objects: ${error.message}`);
|
|
292
|
+
for (const entry of data.objects) {
|
|
293
|
+
const name = entry.key ?? entry.name;
|
|
294
|
+
if (name.split("/").at(-1) === EMPTY_FOLDER_PLACEHOLDER) continue;
|
|
295
|
+
const key = fromObjectName(name);
|
|
296
|
+
if (key === null || !key.startsWith(basePrefix)) continue;
|
|
297
|
+
let storageUri;
|
|
298
|
+
try {
|
|
299
|
+
storageUri = createStorageUri({
|
|
300
|
+
protocol: "supabase-storage",
|
|
301
|
+
bucket: config.bucketName,
|
|
302
|
+
key
|
|
303
|
+
});
|
|
304
|
+
} catch {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
const updatedAt = entry.updated_at ?? entry.created_at;
|
|
308
|
+
objects.push({
|
|
309
|
+
key: key.slice(basePrefix.length),
|
|
310
|
+
...updatedAt ? { lastModifiedAt: new Date(updatedAt) } : {},
|
|
311
|
+
size: Number(entry.metadata?.size ?? 0),
|
|
312
|
+
storageUri
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
cursor = data.hasNext ? data.nextCursor : void 0;
|
|
316
|
+
} while (cursor !== void 0);
|
|
317
|
+
return objects;
|
|
318
|
+
},
|
|
319
|
+
async deleteObjects(relativeKeys) {
|
|
320
|
+
const names = relativeKeys.map((key) => toObjectName(getStorageKey(key)));
|
|
321
|
+
for (let offset = 0; offset < names.length; offset += DELETE_BATCH_SIZE) {
|
|
322
|
+
const { error } = await (await bucket()).remove(names.slice(offset, offset + DELETE_BATCH_SIZE));
|
|
323
|
+
if (error && !isNotFoundError(error)) throw new Error(`Failed to delete storage objects: ${error.message}`);
|
|
324
|
+
}
|
|
231
325
|
}
|
|
232
326
|
});
|
|
233
327
|
};
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import { EngineDatabase, StorageAdapterWith } from "@hot-updater/plugin-core";
|
|
1
|
+
import { EngineDatabase, Lazy, StorageAdapterWith } from "@hot-updater/plugin-core";
|
|
2
2
|
//#region src/supabaseConfig.d.ts
|
|
3
3
|
type SupabaseServiceRoleConfig = {
|
|
4
4
|
supabaseUrl: string;
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* The project's service role key, or a function that returns it, such as
|
|
7
|
+
* `secret("<name>")` in `hot-updater.config.ts`: called on the first
|
|
8
|
+
* request, so a config loads without the key.
|
|
9
|
+
*/
|
|
10
|
+
supabaseServiceRoleKey: Lazy<string>;
|
|
6
11
|
};
|
|
7
12
|
//#endregion
|
|
8
13
|
//#region src/supabaseDatabase.d.ts
|
|
@@ -23,6 +28,6 @@ type SupabaseStorageConfig = SupabaseServiceRoleConfig & {
|
|
|
23
28
|
/** Signed download URL lifetime in seconds. @default 3600 */
|
|
24
29
|
signedUrlExpiresIn?: number;
|
|
25
30
|
};
|
|
26
|
-
declare const supabaseStorage: (config: SupabaseStorageConfig) => StorageAdapterWith<"put" | "get" | "getDownloadUrl" | "exists" | "delete">;
|
|
31
|
+
declare const supabaseStorage: (config: SupabaseStorageConfig) => StorageAdapterWith<"put" | "get" | "getDownloadUrl" | "exists" | "delete" | "listObjects" | "deleteObjects">;
|
|
27
32
|
//#endregion
|
|
28
33
|
export { supabaseDatabase as i, supabaseStorage as n, SupabaseDatabaseConfig as r, SupabaseStorageConfig as t };
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import { EngineDatabase, StorageAdapterWith } from "@hot-updater/plugin-core";
|
|
1
|
+
import { EngineDatabase, Lazy, StorageAdapterWith } from "@hot-updater/plugin-core";
|
|
2
2
|
//#region src/supabaseConfig.d.ts
|
|
3
3
|
type SupabaseServiceRoleConfig = {
|
|
4
4
|
supabaseUrl: string;
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* The project's service role key, or a function that returns it, such as
|
|
7
|
+
* `secret("<name>")` in `hot-updater.config.ts`: called on the first
|
|
8
|
+
* request, so a config loads without the key.
|
|
9
|
+
*/
|
|
10
|
+
supabaseServiceRoleKey: Lazy<string>;
|
|
6
11
|
};
|
|
7
12
|
//#endregion
|
|
8
13
|
//#region src/supabaseDatabase.d.ts
|
|
@@ -23,6 +28,6 @@ type SupabaseStorageConfig = SupabaseServiceRoleConfig & {
|
|
|
23
28
|
/** Signed download URL lifetime in seconds. @default 3600 */
|
|
24
29
|
signedUrlExpiresIn?: number;
|
|
25
30
|
};
|
|
26
|
-
declare const supabaseStorage: (config: SupabaseStorageConfig) => StorageAdapterWith<"put" | "get" | "getDownloadUrl" | "exists" | "delete">;
|
|
31
|
+
declare const supabaseStorage: (config: SupabaseStorageConfig) => StorageAdapterWith<"put" | "get" | "getDownloadUrl" | "exists" | "delete" | "listObjects" | "deleteObjects">;
|
|
27
32
|
//#endregion
|
|
28
33
|
export { supabaseDatabase as i, supabaseStorage as n, SupabaseDatabaseConfig as r, SupabaseStorageConfig as t };
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
|
|
2
2
|
let _supabase_supabase_js = require("@supabase/supabase-js");
|
|
3
3
|
//#region src/supabaseConfig.ts
|
|
4
|
-
const resolveSupabaseServiceRoleKey = (config) => {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
const resolveSupabaseServiceRoleKey = async (config) => {
|
|
5
|
+
const { supabaseServiceRoleKey } = config;
|
|
6
|
+
const key = typeof supabaseServiceRoleKey === "function" ? await supabaseServiceRoleKey() : supabaseServiceRoleKey;
|
|
7
|
+
if (!key) throw new Error("Supabase service role key is required. Set supabaseServiceRoleKey.");
|
|
8
|
+
return key;
|
|
7
9
|
};
|
|
8
10
|
//#endregion
|
|
9
11
|
//#region src/supabaseInfrastructureNames.ts
|
|
@@ -42,9 +44,16 @@ const withCode = (error) => Object.assign(new Error(error.message, { cause: erro
|
|
|
42
44
|
*/
|
|
43
45
|
const supabaseExecutor = (client) => {
|
|
44
46
|
let created;
|
|
45
|
-
const clientOf = () =>
|
|
47
|
+
const clientOf = async () => {
|
|
48
|
+
if (typeof client !== "function") return client;
|
|
49
|
+
created ??= Promise.resolve().then(client).catch((error) => {
|
|
50
|
+
created = void 0;
|
|
51
|
+
throw error;
|
|
52
|
+
});
|
|
53
|
+
return created;
|
|
54
|
+
};
|
|
46
55
|
const apply = async (statements) => {
|
|
47
|
-
const { data, error } = await clientOf().rpc(SUPABASE_APPLY_FUNCTION, { p_statements: statements.map(toApplyStatement) });
|
|
56
|
+
const { data, error } = await (await clientOf()).rpc(SUPABASE_APPLY_FUNCTION, { p_statements: statements.map(toApplyStatement) });
|
|
48
57
|
if (error) throw withCode(error);
|
|
49
58
|
return data;
|
|
50
59
|
};
|
|
@@ -67,7 +76,7 @@ const supabaseExecutor = (client) => {
|
|
|
67
76
|
*/
|
|
68
77
|
const supabaseDatabase = (config) => {
|
|
69
78
|
const { migrations: _migrations, ...adapter } = (0, _hot_updater_plugin_core.createSqlAdapter)({
|
|
70
|
-
executor: supabaseExecutor(() => (0, _supabase_supabase_js.createClient)(config.supabaseUrl, resolveSupabaseServiceRoleKey(config))),
|
|
79
|
+
executor: supabaseExecutor(async () => (0, _supabase_supabase_js.createClient)(config.supabaseUrl, await resolveSupabaseServiceRoleKey(config))),
|
|
71
80
|
tablePrefix: SUPABASE_TABLE_PREFIX
|
|
72
81
|
});
|
|
73
82
|
return (0, _hot_updater_plugin_core.createEngineDatabase)({
|
|
@@ -157,7 +166,15 @@ const isMissingObjectError = async (error) => {
|
|
|
157
166
|
};
|
|
158
167
|
/** A character Storage rejects in an object name, `?`, or `!`. */
|
|
159
168
|
const ESCAPED_CHARACTER = /[^A-Za-z0-9_/.*'() &$=@;:+,-]/gu;
|
|
169
|
+
const ESCAPE_SEQUENCE = /!([0-9A-F]{2})/gu;
|
|
160
170
|
const utf8 = new TextEncoder();
|
|
171
|
+
const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
|
|
172
|
+
/** Objects Storage lists a page at a time. */
|
|
173
|
+
const LIST_PAGE_SIZE = 1e3;
|
|
174
|
+
/** Names Storage removes in one request. */
|
|
175
|
+
const DELETE_BATCH_SIZE = 1e3;
|
|
176
|
+
/** The object the Supabase dashboard keeps in a folder it creates. */
|
|
177
|
+
const EMPTY_FOLDER_PLACEHOLDER = ".emptyFolderPlaceholder";
|
|
161
178
|
/**
|
|
162
179
|
* The object name Storage keeps a key under. Storage accepts names of ASCII
|
|
163
180
|
* letters, digits and `_/!.*'() &$=@;:+,?-` only, and supabase-js puts a name
|
|
@@ -168,12 +185,44 @@ const utf8 = new TextEncoder();
|
|
|
168
185
|
* Deploy's keys, such as `<bundleId>/manifest.json`, are stored as they are.
|
|
169
186
|
*/
|
|
170
187
|
const toObjectName = (key) => key.replace(ESCAPED_CHARACTER, (character) => Array.from(utf8.encode(character), (byte) => `!${byte.toString(16).toUpperCase().padStart(2, "0")}`).join(""));
|
|
188
|
+
/** The key `toObjectName` stored under `name`, or null for a name it never makes. */
|
|
189
|
+
const fromObjectName = (name) => {
|
|
190
|
+
const bytes = [];
|
|
191
|
+
const parts = [];
|
|
192
|
+
const flush = () => {
|
|
193
|
+
if (bytes.length === 0) return true;
|
|
194
|
+
try {
|
|
195
|
+
parts.push(utf8Decoder.decode(new Uint8Array(bytes)));
|
|
196
|
+
} catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
bytes.length = 0;
|
|
200
|
+
return true;
|
|
201
|
+
};
|
|
202
|
+
let index = 0;
|
|
203
|
+
for (const match of name.matchAll(ESCAPE_SEQUENCE)) {
|
|
204
|
+
if (match.index !== index) {
|
|
205
|
+
if (!flush()) return null;
|
|
206
|
+
parts.push(name.slice(index, match.index));
|
|
207
|
+
}
|
|
208
|
+
bytes.push(Number.parseInt(match[1], 16));
|
|
209
|
+
index = match.index + match[0].length;
|
|
210
|
+
}
|
|
211
|
+
if (!flush()) return null;
|
|
212
|
+
parts.push(name.slice(index));
|
|
213
|
+
const key = parts.join("");
|
|
214
|
+
return toObjectName(key) === name ? key : null;
|
|
215
|
+
};
|
|
171
216
|
const supabaseStorage = (config) => {
|
|
172
217
|
let client;
|
|
173
|
-
const bucket = () => (client ??= (0, _supabase_supabase_js.createClient)(config.supabaseUrl,
|
|
218
|
+
const bucket = async () => (await (client ??= resolveSupabaseServiceRoleKey(config).then((key) => (0, _supabase_supabase_js.createClient)(config.supabaseUrl, key), (error) => {
|
|
219
|
+
client = void 0;
|
|
220
|
+
throw error;
|
|
221
|
+
}))).storage.from(config.bucketName);
|
|
174
222
|
const getStorageKey = (0, _hot_updater_plugin_core.createStorageKeyBuilder)(config.basePath);
|
|
223
|
+
const basePrefix = getStorageKey() ? `${getStorageKey()}/` : "";
|
|
175
224
|
const resolveSignedUrl = createSupabaseSignedUrlBatcher({
|
|
176
|
-
createSignedUrls: (_bucketName, keys, expiresIn) => bucket().createSignedUrls(keys, expiresIn),
|
|
225
|
+
createSignedUrls: async (_bucketName, keys, expiresIn) => (await bucket()).createSignedUrls(keys, expiresIn),
|
|
177
226
|
expiresIn: config.signedUrlExpiresIn ?? 3600,
|
|
178
227
|
formatObjectPath: (_bucketName, key) => key
|
|
179
228
|
});
|
|
@@ -187,7 +236,7 @@ const supabaseStorage = (config) => {
|
|
|
187
236
|
protocol: "supabase-storage",
|
|
188
237
|
async put({ key, body, contentLength, contentType }) {
|
|
189
238
|
const storageKey = getStorageKey(key);
|
|
190
|
-
const { error } = await bucket().upload(toObjectName(storageKey), body, {
|
|
239
|
+
const { error } = await (await bucket()).upload(toObjectName(storageKey), body, {
|
|
191
240
|
contentType,
|
|
192
241
|
cacheControl: "max-age=31536000",
|
|
193
242
|
upsert: true,
|
|
@@ -203,7 +252,7 @@ const supabaseStorage = (config) => {
|
|
|
203
252
|
},
|
|
204
253
|
async get({ storageUri }) {
|
|
205
254
|
const { key } = parseAndValidate(storageUri);
|
|
206
|
-
const { data, error } = await bucket().download(toObjectName(key));
|
|
255
|
+
const { data, error } = await (await bucket()).download(toObjectName(key));
|
|
207
256
|
if (error) {
|
|
208
257
|
if (await isMissingObjectError(error)) return { response: null };
|
|
209
258
|
throw new Error(`Failed to download storage object: ${error.message}`);
|
|
@@ -216,7 +265,7 @@ const supabaseStorage = (config) => {
|
|
|
216
265
|
},
|
|
217
266
|
async exists({ storageUri }) {
|
|
218
267
|
const { key } = parseAndValidate(storageUri);
|
|
219
|
-
const { data, error } = await bucket().exists(toObjectName(key));
|
|
268
|
+
const { data, error } = await (await bucket()).exists(toObjectName(key));
|
|
220
269
|
if (error) {
|
|
221
270
|
if (isMissingExistsError(error)) return { exists: false };
|
|
222
271
|
throw error;
|
|
@@ -225,9 +274,54 @@ const supabaseStorage = (config) => {
|
|
|
225
274
|
},
|
|
226
275
|
async delete({ storageUri }) {
|
|
227
276
|
const { key } = parseAndValidate(storageUri);
|
|
228
|
-
const { error } = await bucket().remove([toObjectName(key)]);
|
|
277
|
+
const { error } = await (await bucket()).remove([toObjectName(key)]);
|
|
229
278
|
if (error && !isNotFoundError(error)) throw new Error(`Failed to delete storage object: ${error.message}`);
|
|
230
279
|
return { deleted: true };
|
|
280
|
+
},
|
|
281
|
+
async listObjects(prefix) {
|
|
282
|
+
const objects = [];
|
|
283
|
+
const folder = toObjectName(getStorageKey(prefix ?? ""));
|
|
284
|
+
let cursor;
|
|
285
|
+
do {
|
|
286
|
+
const { data, error } = await (await bucket()).listV2({
|
|
287
|
+
prefix: folder ? `${folder}/` : "",
|
|
288
|
+
limit: LIST_PAGE_SIZE,
|
|
289
|
+
...cursor === void 0 ? {} : { cursor }
|
|
290
|
+
});
|
|
291
|
+
if (error) throw new Error(`Failed to list storage objects: ${error.message}`);
|
|
292
|
+
for (const entry of data.objects) {
|
|
293
|
+
const name = entry.key ?? entry.name;
|
|
294
|
+
if (name.split("/").at(-1) === EMPTY_FOLDER_PLACEHOLDER) continue;
|
|
295
|
+
const key = fromObjectName(name);
|
|
296
|
+
if (key === null || !key.startsWith(basePrefix)) continue;
|
|
297
|
+
let storageUri;
|
|
298
|
+
try {
|
|
299
|
+
storageUri = (0, _hot_updater_plugin_core.createStorageUri)({
|
|
300
|
+
protocol: "supabase-storage",
|
|
301
|
+
bucket: config.bucketName,
|
|
302
|
+
key
|
|
303
|
+
});
|
|
304
|
+
} catch {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
const updatedAt = entry.updated_at ?? entry.created_at;
|
|
308
|
+
objects.push({
|
|
309
|
+
key: key.slice(basePrefix.length),
|
|
310
|
+
...updatedAt ? { lastModifiedAt: new Date(updatedAt) } : {},
|
|
311
|
+
size: Number(entry.metadata?.size ?? 0),
|
|
312
|
+
storageUri
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
cursor = data.hasNext ? data.nextCursor : void 0;
|
|
316
|
+
} while (cursor !== void 0);
|
|
317
|
+
return objects;
|
|
318
|
+
},
|
|
319
|
+
async deleteObjects(relativeKeys) {
|
|
320
|
+
const names = relativeKeys.map((key) => toObjectName(getStorageKey(key)));
|
|
321
|
+
for (let offset = 0; offset < names.length; offset += DELETE_BATCH_SIZE) {
|
|
322
|
+
const { error } = await (await bucket()).remove(names.slice(offset, offset + DELETE_BATCH_SIZE));
|
|
323
|
+
if (error && !isNotFoundError(error)) throw new Error(`Failed to delete storage objects: ${error.message}`);
|
|
324
|
+
}
|
|
231
325
|
}
|
|
232
326
|
});
|
|
233
327
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/supabase",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.2.0-rc.
|
|
4
|
+
"version": "1.2.0-rc.11",
|
|
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.2.0-rc.
|
|
55
|
-
"@hot-updater/plugin-core": "1.2.0-rc.
|
|
56
|
-
"@hot-updater/cli-tools": "1.2.0-rc.
|
|
57
|
-
"@hot-updater/server": "1.2.0-rc.
|
|
54
|
+
"@hot-updater/protocol": "1.2.0-rc.11",
|
|
55
|
+
"@hot-updater/plugin-core": "1.2.0-rc.11",
|
|
56
|
+
"@hot-updater/cli-tools": "1.2.0-rc.11",
|
|
57
|
+
"@hot-updater/server": "1.2.0-rc.11"
|
|
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/
|
|
67
|
+
"@hot-updater/mock": "1.2.0-rc.11",
|
|
68
|
+
"@hot-updater/test-utils": "1.2.0-rc.11",
|
|
69
|
+
"@hot-updater/postgres": "1.2.0-rc.11"
|
|
70
70
|
},
|
|
71
71
|
"scripts": {
|
|
72
72
|
"build": "tsdown",
|