@hot-updater/supabase 1.0.0-rc.40 → 1.0.0-rc.42

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 CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_plugins = require("./plugins-jpIkI_T2.cjs");
2
+ const require_plugins = require("./plugins-C3qJSy3_.cjs");
3
3
  //#region src/supabaseEdgeFunctionStorage.ts
4
4
  const supabaseEdgeFunctionStorage = (config) => require_plugins.supabaseStorage(config);
5
5
  //#endregion
package/dist/edge.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { i as plugins, n as supabaseDatabase, t as SupabaseDatabaseConfig } from "./supabaseDatabase-DPvSBD18.cjs";
1
+ import { i as plugins, n as supabaseDatabase, t as SupabaseDatabaseConfig } from "./supabaseDatabase-DV5XdQWp.cjs";
2
2
  import { StorageAdapterWith } from "@hot-updater/plugin-core";
3
3
  //#region src/supabaseEdgeFunctionStorage.d.ts
4
4
  interface SupabaseEdgeFunctionStorageConfig {
package/dist/edge.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { i as plugins, n as supabaseDatabase, t as SupabaseDatabaseConfig } from "./supabaseDatabase-DPvSBD18.mjs";
1
+ import { i as plugins, n as supabaseDatabase, t as SupabaseDatabaseConfig } from "./supabaseDatabase-DV5XdQWp.mjs";
2
2
  import { StorageAdapterWith } from "@hot-updater/plugin-core";
3
3
  //#region src/supabaseEdgeFunctionStorage.d.ts
4
4
  interface SupabaseEdgeFunctionStorageConfig {
package/dist/edge.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as supabaseStorage, r as supabaseDatabase, t as plugins } from "./plugins-Dj-4Pk3j.mjs";
1
+ import { n as supabaseStorage, r as supabaseDatabase, t as plugins } from "./plugins-Do0arFjS.mjs";
2
2
  //#region src/supabaseEdgeFunctionStorage.ts
3
3
  const supabaseEdgeFunctionStorage = (config) => supabaseStorage(config);
4
4
  //#endregion
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_plugins = require("./plugins-jpIkI_T2.cjs");
2
+ const require_plugins = require("./plugins-C3qJSy3_.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 { i as plugins, r as SupabaseServiceRoleConfig, t as SupabaseDatabaseConfig } from "./supabaseDatabase-DPvSBD18.cjs";
1
+ import { i as plugins, r as SupabaseServiceRoleConfig, t as SupabaseDatabaseConfig } from "./supabaseDatabase-DV5XdQWp.cjs";
2
2
  import { StorageAdapterWith, 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 { i as plugins, r as SupabaseServiceRoleConfig, t as SupabaseDatabaseConfig } from "./supabaseDatabase-DPvSBD18.mjs";
1
+ import { i as plugins, r as SupabaseServiceRoleConfig, t as SupabaseDatabaseConfig } from "./supabaseDatabase-DV5XdQWp.mjs";
2
2
  import { StorageAdapterWith, 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 { 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";
1
+ import { a as SUPABASE_TABLE_PREFIX, i as SUPABASE_APPLY_FUNCTION, n as supabaseStorage, r as supabaseDatabase$1, t as plugins } from "./plugins-Do0arFjS.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. */
@@ -48,6 +48,7 @@ let node_stream = require("node:stream");
48
48
  let node_buffer = require("node:buffer");
49
49
  let _hot_updater_server_plugins_api_keys = require("@hot-updater/server/plugins/api-keys");
50
50
  let _hot_updater_server_plugins_insights = require("@hot-updater/server/plugins/insights");
51
+ let _hot_updater_server_plugins_remote_config = require("@hot-updater/server/plugins/remote-config");
51
52
  let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
52
53
  let _supabase_supabase_js = require("@supabase/supabase-js");
53
54
  //#region ../../node_modules/.pnpm/es-toolkit@1.47.0/node_modules/es-toolkit/dist/_internal/globalThis.mjs
@@ -6335,10 +6336,14 @@ const { sendMessage, getOneMessage, getEachMessage, getCancelSignal } = getIpcEx
6335
6336
  //#endregion
6336
6337
  //#region src/plugins.ts
6337
6338
  /**
6338
- * The plugins the managed Supabase server runs: Insights, and API keys on every
6339
- * client route. Its Edge Function imports this list.
6339
+ * The plugins the managed Supabase server runs: Insights, API keys on every
6340
+ * client route, and Remote Config. Its Edge Function imports this list.
6340
6341
  */
6341
- const plugins = [(0, _hot_updater_server_plugins_insights.insights)(), (0, _hot_updater_server_plugins_api_keys.apiKeys)()];
6342
+ const plugins = [
6343
+ (0, _hot_updater_server_plugins_insights.insights)(),
6344
+ (0, _hot_updater_server_plugins_api_keys.apiKeys)(),
6345
+ (0, _hot_updater_server_plugins_remote_config.remoteConfig)()
6346
+ ];
6342
6347
  //#endregion
6343
6348
  //#region src/supabaseConfig.ts
6344
6349
  const resolveSupabaseServiceRoleKey = (config) => {
@@ -19,6 +19,7 @@ import { Duplex, PassThrough, Readable, Transform, Writable, getDefaultHighWater
19
19
  import { Buffer as Buffer$1 } from "node:buffer";
20
20
  import { apiKeys } from "@hot-updater/server/plugins/api-keys";
21
21
  import { insights } from "@hot-updater/server/plugins/insights";
22
+ import { remoteConfig } from "@hot-updater/server/plugins/remote-config";
22
23
  import { SETTINGS_TABLE, createEngineDatabase, createSqlAdapter } from "@hot-updater/plugin-core";
23
24
  import { createClient } from "@supabase/supabase-js";
24
25
  //#region \0rolldown/runtime.js
@@ -6330,10 +6331,14 @@ const { sendMessage, getOneMessage, getEachMessage, getCancelSignal } = getIpcEx
6330
6331
  //#endregion
6331
6332
  //#region src/plugins.ts
6332
6333
  /**
6333
- * The plugins the managed Supabase server runs: Insights, and API keys on every
6334
- * client route. Its Edge Function imports this list.
6334
+ * The plugins the managed Supabase server runs: Insights, API keys on every
6335
+ * client route, and Remote Config. Its Edge Function imports this list.
6335
6336
  */
6336
- const plugins = [insights(), apiKeys()];
6337
+ const plugins = [
6338
+ insights(),
6339
+ apiKeys(),
6340
+ remoteConfig()
6341
+ ];
6337
6342
  //#endregion
6338
6343
  //#region src/supabaseConfig.ts
6339
6344
  const resolveSupabaseServiceRoleKey = (config) => {
@@ -2,6 +2,7 @@ let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
2
2
  let _supabase_supabase_js = require("@supabase/supabase-js");
3
3
  let _hot_updater_server_plugins_api_keys = require("@hot-updater/server/plugins/api-keys");
4
4
  let _hot_updater_server_plugins_insights = require("@hot-updater/server/plugins/insights");
5
+ let _hot_updater_server_plugins_remote_config = require("@hot-updater/server/plugins/remote-config");
5
6
  //#region src/supabaseConfig.ts
6
7
  const resolveSupabaseServiceRoleKey = (config) => {
7
8
  if (!config.supabaseServiceRoleKey) throw new Error("Supabase service role key is required. Set supabaseServiceRoleKey.");
@@ -232,10 +233,14 @@ const supabaseStorage = (config) => {
232
233
  //#endregion
233
234
  //#region src/plugins.ts
234
235
  /**
235
- * The plugins the managed Supabase server runs: Insights, and API keys on every
236
- * client route. Its Edge Function imports this list.
236
+ * The plugins the managed Supabase server runs: Insights, API keys on every
237
+ * client route, and Remote Config. Its Edge Function imports this list.
237
238
  */
238
- const plugins = [(0, _hot_updater_server_plugins_insights.insights)(), (0, _hot_updater_server_plugins_api_keys.apiKeys)()];
239
+ const plugins = [
240
+ (0, _hot_updater_server_plugins_insights.insights)(),
241
+ (0, _hot_updater_server_plugins_api_keys.apiKeys)(),
242
+ (0, _hot_updater_server_plugins_remote_config.remoteConfig)()
243
+ ];
239
244
  //#endregion
240
245
  Object.defineProperty(exports, "SUPABASE_APPLY_FUNCTION", {
241
246
  enumerable: true,
@@ -2,6 +2,7 @@ import { SETTINGS_TABLE, createEngineDatabase, createSqlAdapter, createStorageAd
2
2
  import { createClient } from "@supabase/supabase-js";
3
3
  import { apiKeys } from "@hot-updater/server/plugins/api-keys";
4
4
  import { insights } from "@hot-updater/server/plugins/insights";
5
+ import { remoteConfig } from "@hot-updater/server/plugins/remote-config";
5
6
  //#region src/supabaseConfig.ts
6
7
  const resolveSupabaseServiceRoleKey = (config) => {
7
8
  if (!config.supabaseServiceRoleKey) throw new Error("Supabase service role key is required. Set supabaseServiceRoleKey.");
@@ -232,9 +233,13 @@ const supabaseStorage = (config) => {
232
233
  //#endregion
233
234
  //#region src/plugins.ts
234
235
  /**
235
- * The plugins the managed Supabase server runs: Insights, and API keys on every
236
- * client route. Its Edge Function imports this list.
236
+ * The plugins the managed Supabase server runs: Insights, API keys on every
237
+ * client route, and Remote Config. Its Edge Function imports this list.
237
238
  */
238
- const plugins = [insights(), apiKeys()];
239
+ const plugins = [
240
+ insights(),
241
+ apiKeys(),
242
+ remoteConfig()
243
+ ];
239
244
  //#endregion
240
245
  export { SUPABASE_TABLE_PREFIX as a, SUPABASE_APPLY_FUNCTION as i, supabaseStorage as n, supabaseDatabase as r, plugins as t };
@@ -330,10 +330,198 @@ interface CreatedApiKey {
330
330
  readonly record: ApiKeyMetadata;
331
331
  }
332
332
  //#endregion
333
+ //#region ../remote-config/dist/shared/wire.d.mts
334
+ //#region src/shared/wire.d.ts
335
+ /** What a device tells the server, so the server can evaluate conditions. */
336
+ interface RemoteConfigDeviceContext {
337
+ readonly platform: "ios" | "android";
338
+ /** The native app version, or null when the build has none. */
339
+ readonly appVersion: string | null;
340
+ readonly channel: string;
341
+ /** A numeric cohort from 1 to 1000, or a custom cohort's slug. */
342
+ readonly cohort: string;
343
+ /** The native build's fingerprint, or null without one. */
344
+ readonly fingerprintHash: string | null;
345
+ }
346
+ /** The body of a `200` from `GET /remote-config`. */
347
+ interface RemoteConfigFetchResponse {
348
+ /** The published template version the values come from; 0 before the first publish. */
349
+ readonly version: number;
350
+ /**
351
+ * Each parameter's value for this device, as text. A parameter whose
352
+ * value is the app's own default is left out.
353
+ */
354
+ readonly values: Readonly<Record<string, string>>;
355
+ }
356
+ //#endregion
357
+ //#region ../remote-config/dist/server/template.d.mts
358
+ //#region src/server/template.d.ts
359
+ /**
360
+ * How the Console edits a parameter's value and checks it before a publish.
361
+ * Devices receive every value as text.
362
+ */
363
+ type RemoteConfigValueType = "STRING" | "NUMBER" | "BOOLEAN" | "JSON";
364
+ /** A value a parameter takes: text, or the app's own default. */
365
+ type RemoteConfigParameterValue = {
366
+ readonly value: string;
367
+ } | {
368
+ readonly useInAppDefault: true;
369
+ };
370
+ interface RemoteConfigParameter {
371
+ readonly valueType: RemoteConfigValueType;
372
+ /** The value when none of the parameter's conditions match. */
373
+ readonly defaultValue: RemoteConfigParameterValue;
374
+ /**
375
+ * Values by condition name. When several of these conditions match, the
376
+ * one listed first in the template's `conditions` wins.
377
+ */
378
+ readonly conditionalValues?: Readonly<Record<string, RemoteConfigParameterValue>>;
379
+ readonly description?: string;
380
+ }
381
+ /** A test on a device's context; a condition matches when all of its rules do. */
382
+ type RemoteConfigRule = {
383
+ readonly type: "platform";
384
+ readonly platforms: readonly ("ios" | "android")[];
385
+ } | {
386
+ readonly type: "channel";
387
+ readonly channels: readonly string[];
388
+ } |
389
+ /** A semver range, such as `>=1.4.0` or `2.x`, as release targets use. */
390
+ {
391
+ readonly type: "appVersion";
392
+ readonly range: string;
393
+ } |
394
+ /** Devices whose cohort is one of these: numbers from 1 to 1000 or slugs. */
395
+ {
396
+ readonly type: "cohort";
397
+ readonly cohorts: readonly string[];
398
+ } |
399
+ /**
400
+ * A share of numeric cohorts: the devices whose cohort lands in
401
+ * `[from, to)` percent once `seed` shuffles the 1,000 cohorts. Rules with
402
+ * one seed pick from one order, so `0–10` and `10–20` never overlap; a new
403
+ * seed picks other devices. Custom cohorts never match.
404
+ */
405
+ {
406
+ readonly type: "percent";
407
+ readonly seed: string;
408
+ readonly from: number;
409
+ readonly to: number;
410
+ } | {
411
+ readonly type: "fingerprint";
412
+ readonly hashes: readonly string[];
413
+ } |
414
+ /**
415
+ * While the server's clock is in `[from, to)`: ISO 8601 date-times in UTC,
416
+ * either of them open. A device gets the change at its next fetch.
417
+ */
418
+ {
419
+ readonly type: "dateTime";
420
+ readonly from?: string;
421
+ readonly to?: string;
422
+ };
423
+ interface RemoteConfigCondition {
424
+ /** Unique in the template; parameters name it in `conditionalValues`. */
425
+ readonly name: string;
426
+ /** Every rule must match. */
427
+ readonly rules: readonly RemoteConfigRule[];
428
+ }
429
+ /**
430
+ * Everything Remote Config serves, published as a whole and versioned.
431
+ */
432
+ interface RemoteConfigTemplate {
433
+ /** In priority order: the first one that matches decides a parameter's value. */
434
+ readonly conditions: readonly RemoteConfigCondition[];
435
+ readonly parameters: Readonly<Record<string, RemoteConfigParameter>>;
436
+ }
437
+ /** What the server knows of a device; a rule on a missing field never matches. */
438
+ type RemoteConfigEvaluationContext = Partial<Readonly<{ [K in keyof RemoteConfigDeviceContext]: RemoteConfigDeviceContext[K] | null; }>> & {
439
+ /**
440
+ * When to evaluate, in ms since the epoch. The server uses its own clock;
441
+ * a `dateTime` rule never matches without it.
442
+ */
443
+ readonly now?: number;
444
+ };
445
+ //#endregion
446
+ //#region ../remote-config/dist/server/api.d.mts
447
+ //#region src/server/api.d.ts
448
+ /** A published version's record, without its template. */
449
+ interface RemoteConfigVersion {
450
+ readonly version: number;
451
+ readonly description: string | null;
452
+ /** `ROLLBACK` when the version copies `rollbackSource`. */
453
+ readonly updateType: "PUBLISH" | "ROLLBACK";
454
+ readonly rollbackSource: number | null;
455
+ readonly createdAtMs: number;
456
+ }
457
+ interface RemoteConfigVersionDetail extends RemoteConfigVersion {
458
+ readonly template: RemoteConfigTemplate;
459
+ }
460
+ /** The template devices receive now. */
461
+ interface RemoteConfigActive {
462
+ /** 0, with an empty template, before the first publish. */
463
+ readonly version: number;
464
+ readonly template: RemoteConfigTemplate;
465
+ readonly updatedAtMs: number | null;
466
+ }
467
+ /**
468
+ * How a publish or a rollback ended: `conflict` when another publish moved
469
+ * the active version past `baseVersion` first, so a stale edit never
470
+ * replaces a newer template.
471
+ */
472
+ type RemoteConfigPublishResult = {
473
+ readonly status: "published";
474
+ readonly version: RemoteConfigVersion;
475
+ } | {
476
+ readonly status: "conflict";
477
+ readonly currentVersion: number;
478
+ };
479
+ interface RemoteConfigVersionsPage {
480
+ /** Newest first. */
481
+ readonly versions: readonly RemoteConfigVersion[];
482
+ /** Pass as `cursor` for the next page; absent on the last one. */
483
+ readonly next?: string;
484
+ }
485
+ /** `hotUpdater.api.remoteConfig`. */
486
+ interface RemoteConfigApi {
487
+ /** The active template and its version. */
488
+ getActive(): Promise<RemoteConfigActive>;
489
+ /**
490
+ * Validates and publishes a template as the next version. Throws
491
+ * `RemoteConfigValidationError` for an invalid template.
492
+ */
493
+ publish(input: {
494
+ readonly template: unknown;
495
+ /** The version the edit started from; 0 before the first publish. */
496
+ readonly baseVersion: number;
497
+ readonly description?: string;
498
+ }): Promise<RemoteConfigPublishResult>;
499
+ /** Publishes a copy of an earlier version; `not_found` when it does not exist. */
500
+ rollback(input: {
501
+ readonly version: number;
502
+ readonly baseVersion: number;
503
+ readonly description?: string;
504
+ }): Promise<RemoteConfigPublishResult | {
505
+ readonly status: "not_found";
506
+ }>;
507
+ /** One page of published versions, newest first. */
508
+ listVersions(input?: {
509
+ readonly limit?: number;
510
+ readonly cursor?: string;
511
+ }): Promise<RemoteConfigVersionsPage>;
512
+ /** One version with its template, or null. */
513
+ getVersion(version: number): Promise<RemoteConfigVersionDetail | null>;
514
+ /**
515
+ * What a device with this context receives from `GET /remote-config`, at
516
+ * `context.now` or, without it, now.
517
+ */
518
+ resolve(context: RemoteConfigEvaluationContext): Promise<RemoteConfigFetchResponse>;
519
+ }
520
+ //#endregion
333
521
  //#region src/plugins.d.ts
334
522
  /**
335
- * The plugins the managed Supabase server runs: Insights, and API keys on every
336
- * client route. Its Edge Function imports this list.
523
+ * The plugins the managed Supabase server runs: Insights, API keys on every
524
+ * client route, and Remote Config. Its Edge Function imports this list.
337
525
  */
338
526
  declare const plugins: readonly [import("@hot-updater/plugin-core").HotUpdaterPlugin<"insights", {
339
527
  readonly bundle_events: import("@hot-updater/plugin-core").TableDefinition<{
@@ -775,7 +963,55 @@ declare const plugins: readonly [import("@hot-updater/plugin-core").HotUpdaterPl
775
963
  };
776
964
  }, {
777
965
  readonly clientAuth: true;
778
- }>];
966
+ }>, import("@hot-updater/plugin-core").HotUpdaterPlugin<"remoteConfig", {
967
+ readonly remote_config_active: import("@hot-updater/plugin-core").TableDefinition<{
968
+ readonly id: {
969
+ readonly type: "string";
970
+ readonly maxLength: 16;
971
+ };
972
+ readonly version: {
973
+ readonly type: "integer";
974
+ };
975
+ readonly template: {
976
+ readonly type: "json";
977
+ };
978
+ readonly updated_at_ms: {
979
+ readonly type: "integer";
980
+ };
981
+ }, {}, {}, readonly ["id"]>;
982
+ readonly remote_config_versions: import("@hot-updater/plugin-core").TableDefinition<{
983
+ readonly version: {
984
+ readonly type: "integer";
985
+ };
986
+ readonly template: {
987
+ readonly type: "json";
988
+ };
989
+ readonly description: {
990
+ readonly type: "string";
991
+ readonly maxLength: 256;
992
+ readonly required: false;
993
+ };
994
+ readonly update_type: {
995
+ readonly type: "string";
996
+ readonly maxLength: 16;
997
+ };
998
+ readonly rollback_source: {
999
+ readonly type: "integer";
1000
+ readonly required: false;
1001
+ };
1002
+ readonly created_at_ms: {
1003
+ readonly type: "integer";
1004
+ };
1005
+ }, {}, {
1006
+ readonly byVersion: {
1007
+ readonly eq: readonly [];
1008
+ readonly sort: readonly ["version"];
1009
+ };
1010
+ }, readonly ["version"]>;
1011
+ }, {
1012
+ api: RemoteConfigApi;
1013
+ endpoints: readonly import("@hot-updater/plugin-core").PluginEndpoint[];
1014
+ }, {}>];
779
1015
  //#endregion
780
1016
  //#region src/supabaseConfig.d.ts
781
1017
  type SupabaseServiceRoleConfig = {
@@ -330,10 +330,198 @@ interface CreatedApiKey {
330
330
  readonly record: ApiKeyMetadata;
331
331
  }
332
332
  //#endregion
333
+ //#region ../remote-config/dist/shared/wire.d.mts
334
+ //#region src/shared/wire.d.ts
335
+ /** What a device tells the server, so the server can evaluate conditions. */
336
+ interface RemoteConfigDeviceContext {
337
+ readonly platform: "ios" | "android";
338
+ /** The native app version, or null when the build has none. */
339
+ readonly appVersion: string | null;
340
+ readonly channel: string;
341
+ /** A numeric cohort from 1 to 1000, or a custom cohort's slug. */
342
+ readonly cohort: string;
343
+ /** The native build's fingerprint, or null without one. */
344
+ readonly fingerprintHash: string | null;
345
+ }
346
+ /** The body of a `200` from `GET /remote-config`. */
347
+ interface RemoteConfigFetchResponse {
348
+ /** The published template version the values come from; 0 before the first publish. */
349
+ readonly version: number;
350
+ /**
351
+ * Each parameter's value for this device, as text. A parameter whose
352
+ * value is the app's own default is left out.
353
+ */
354
+ readonly values: Readonly<Record<string, string>>;
355
+ }
356
+ //#endregion
357
+ //#region ../remote-config/dist/server/template.d.mts
358
+ //#region src/server/template.d.ts
359
+ /**
360
+ * How the Console edits a parameter's value and checks it before a publish.
361
+ * Devices receive every value as text.
362
+ */
363
+ type RemoteConfigValueType = "STRING" | "NUMBER" | "BOOLEAN" | "JSON";
364
+ /** A value a parameter takes: text, or the app's own default. */
365
+ type RemoteConfigParameterValue = {
366
+ readonly value: string;
367
+ } | {
368
+ readonly useInAppDefault: true;
369
+ };
370
+ interface RemoteConfigParameter {
371
+ readonly valueType: RemoteConfigValueType;
372
+ /** The value when none of the parameter's conditions match. */
373
+ readonly defaultValue: RemoteConfigParameterValue;
374
+ /**
375
+ * Values by condition name. When several of these conditions match, the
376
+ * one listed first in the template's `conditions` wins.
377
+ */
378
+ readonly conditionalValues?: Readonly<Record<string, RemoteConfigParameterValue>>;
379
+ readonly description?: string;
380
+ }
381
+ /** A test on a device's context; a condition matches when all of its rules do. */
382
+ type RemoteConfigRule = {
383
+ readonly type: "platform";
384
+ readonly platforms: readonly ("ios" | "android")[];
385
+ } | {
386
+ readonly type: "channel";
387
+ readonly channels: readonly string[];
388
+ } |
389
+ /** A semver range, such as `>=1.4.0` or `2.x`, as release targets use. */
390
+ {
391
+ readonly type: "appVersion";
392
+ readonly range: string;
393
+ } |
394
+ /** Devices whose cohort is one of these: numbers from 1 to 1000 or slugs. */
395
+ {
396
+ readonly type: "cohort";
397
+ readonly cohorts: readonly string[];
398
+ } |
399
+ /**
400
+ * A share of numeric cohorts: the devices whose cohort lands in
401
+ * `[from, to)` percent once `seed` shuffles the 1,000 cohorts. Rules with
402
+ * one seed pick from one order, so `0–10` and `10–20` never overlap; a new
403
+ * seed picks other devices. Custom cohorts never match.
404
+ */
405
+ {
406
+ readonly type: "percent";
407
+ readonly seed: string;
408
+ readonly from: number;
409
+ readonly to: number;
410
+ } | {
411
+ readonly type: "fingerprint";
412
+ readonly hashes: readonly string[];
413
+ } |
414
+ /**
415
+ * While the server's clock is in `[from, to)`: ISO 8601 date-times in UTC,
416
+ * either of them open. A device gets the change at its next fetch.
417
+ */
418
+ {
419
+ readonly type: "dateTime";
420
+ readonly from?: string;
421
+ readonly to?: string;
422
+ };
423
+ interface RemoteConfigCondition {
424
+ /** Unique in the template; parameters name it in `conditionalValues`. */
425
+ readonly name: string;
426
+ /** Every rule must match. */
427
+ readonly rules: readonly RemoteConfigRule[];
428
+ }
429
+ /**
430
+ * Everything Remote Config serves, published as a whole and versioned.
431
+ */
432
+ interface RemoteConfigTemplate {
433
+ /** In priority order: the first one that matches decides a parameter's value. */
434
+ readonly conditions: readonly RemoteConfigCondition[];
435
+ readonly parameters: Readonly<Record<string, RemoteConfigParameter>>;
436
+ }
437
+ /** What the server knows of a device; a rule on a missing field never matches. */
438
+ type RemoteConfigEvaluationContext = Partial<Readonly<{ [K in keyof RemoteConfigDeviceContext]: RemoteConfigDeviceContext[K] | null; }>> & {
439
+ /**
440
+ * When to evaluate, in ms since the epoch. The server uses its own clock;
441
+ * a `dateTime` rule never matches without it.
442
+ */
443
+ readonly now?: number;
444
+ };
445
+ //#endregion
446
+ //#region ../remote-config/dist/server/api.d.mts
447
+ //#region src/server/api.d.ts
448
+ /** A published version's record, without its template. */
449
+ interface RemoteConfigVersion {
450
+ readonly version: number;
451
+ readonly description: string | null;
452
+ /** `ROLLBACK` when the version copies `rollbackSource`. */
453
+ readonly updateType: "PUBLISH" | "ROLLBACK";
454
+ readonly rollbackSource: number | null;
455
+ readonly createdAtMs: number;
456
+ }
457
+ interface RemoteConfigVersionDetail extends RemoteConfigVersion {
458
+ readonly template: RemoteConfigTemplate;
459
+ }
460
+ /** The template devices receive now. */
461
+ interface RemoteConfigActive {
462
+ /** 0, with an empty template, before the first publish. */
463
+ readonly version: number;
464
+ readonly template: RemoteConfigTemplate;
465
+ readonly updatedAtMs: number | null;
466
+ }
467
+ /**
468
+ * How a publish or a rollback ended: `conflict` when another publish moved
469
+ * the active version past `baseVersion` first, so a stale edit never
470
+ * replaces a newer template.
471
+ */
472
+ type RemoteConfigPublishResult = {
473
+ readonly status: "published";
474
+ readonly version: RemoteConfigVersion;
475
+ } | {
476
+ readonly status: "conflict";
477
+ readonly currentVersion: number;
478
+ };
479
+ interface RemoteConfigVersionsPage {
480
+ /** Newest first. */
481
+ readonly versions: readonly RemoteConfigVersion[];
482
+ /** Pass as `cursor` for the next page; absent on the last one. */
483
+ readonly next?: string;
484
+ }
485
+ /** `hotUpdater.api.remoteConfig`. */
486
+ interface RemoteConfigApi {
487
+ /** The active template and its version. */
488
+ getActive(): Promise<RemoteConfigActive>;
489
+ /**
490
+ * Validates and publishes a template as the next version. Throws
491
+ * `RemoteConfigValidationError` for an invalid template.
492
+ */
493
+ publish(input: {
494
+ readonly template: unknown;
495
+ /** The version the edit started from; 0 before the first publish. */
496
+ readonly baseVersion: number;
497
+ readonly description?: string;
498
+ }): Promise<RemoteConfigPublishResult>;
499
+ /** Publishes a copy of an earlier version; `not_found` when it does not exist. */
500
+ rollback(input: {
501
+ readonly version: number;
502
+ readonly baseVersion: number;
503
+ readonly description?: string;
504
+ }): Promise<RemoteConfigPublishResult | {
505
+ readonly status: "not_found";
506
+ }>;
507
+ /** One page of published versions, newest first. */
508
+ listVersions(input?: {
509
+ readonly limit?: number;
510
+ readonly cursor?: string;
511
+ }): Promise<RemoteConfigVersionsPage>;
512
+ /** One version with its template, or null. */
513
+ getVersion(version: number): Promise<RemoteConfigVersionDetail | null>;
514
+ /**
515
+ * What a device with this context receives from `GET /remote-config`, at
516
+ * `context.now` or, without it, now.
517
+ */
518
+ resolve(context: RemoteConfigEvaluationContext): Promise<RemoteConfigFetchResponse>;
519
+ }
520
+ //#endregion
333
521
  //#region src/plugins.d.ts
334
522
  /**
335
- * The plugins the managed Supabase server runs: Insights, and API keys on every
336
- * client route. Its Edge Function imports this list.
523
+ * The plugins the managed Supabase server runs: Insights, API keys on every
524
+ * client route, and Remote Config. Its Edge Function imports this list.
337
525
  */
338
526
  declare const plugins: readonly [import("@hot-updater/plugin-core").HotUpdaterPlugin<"insights", {
339
527
  readonly bundle_events: import("@hot-updater/plugin-core").TableDefinition<{
@@ -775,7 +963,55 @@ declare const plugins: readonly [import("@hot-updater/plugin-core").HotUpdaterPl
775
963
  };
776
964
  }, {
777
965
  readonly clientAuth: true;
778
- }>];
966
+ }>, import("@hot-updater/plugin-core").HotUpdaterPlugin<"remoteConfig", {
967
+ readonly remote_config_active: import("@hot-updater/plugin-core").TableDefinition<{
968
+ readonly id: {
969
+ readonly type: "string";
970
+ readonly maxLength: 16;
971
+ };
972
+ readonly version: {
973
+ readonly type: "integer";
974
+ };
975
+ readonly template: {
976
+ readonly type: "json";
977
+ };
978
+ readonly updated_at_ms: {
979
+ readonly type: "integer";
980
+ };
981
+ }, {}, {}, readonly ["id"]>;
982
+ readonly remote_config_versions: import("@hot-updater/plugin-core").TableDefinition<{
983
+ readonly version: {
984
+ readonly type: "integer";
985
+ };
986
+ readonly template: {
987
+ readonly type: "json";
988
+ };
989
+ readonly description: {
990
+ readonly type: "string";
991
+ readonly maxLength: 256;
992
+ readonly required: false;
993
+ };
994
+ readonly update_type: {
995
+ readonly type: "string";
996
+ readonly maxLength: 16;
997
+ };
998
+ readonly rollback_source: {
999
+ readonly type: "integer";
1000
+ readonly required: false;
1001
+ };
1002
+ readonly created_at_ms: {
1003
+ readonly type: "integer";
1004
+ };
1005
+ }, {}, {
1006
+ readonly byVersion: {
1007
+ readonly eq: readonly [];
1008
+ readonly sort: readonly ["version"];
1009
+ };
1010
+ }, readonly ["version"]>;
1011
+ }, {
1012
+ api: RemoteConfigApi;
1013
+ endpoints: readonly import("@hot-updater/plugin-core").PluginEndpoint[];
1014
+ }, {}>];
779
1015
  //#endregion
780
1016
  //#region src/supabaseConfig.d.ts
781
1017
  type SupabaseServiceRoleConfig = {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hot-updater/supabase",
3
3
  "type": "module",
4
- "version": "1.0.0-rc.40",
4
+ "version": "1.0.0-rc.42",
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.0.0-rc.40",
55
- "@hot-updater/cli-tools": "1.0.0-rc.40",
56
- "@hot-updater/server": "1.0.0-rc.40",
57
- "@hot-updater/plugin-core": "1.0.0-rc.40"
54
+ "@hot-updater/protocol": "1.0.0-rc.42",
55
+ "@hot-updater/plugin-core": "1.0.0-rc.42",
56
+ "@hot-updater/cli-tools": "1.0.0-rc.42",
57
+ "@hot-updater/server": "1.0.0-rc.42"
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/test-utils": "1.0.0-rc.40",
68
- "@hot-updater/postgres": "1.0.0-rc.40",
69
- "@hot-updater/mock": "1.0.0-rc.40"
67
+ "@hot-updater/mock": "1.0.0-rc.42",
68
+ "@hot-updater/test-utils": "1.0.0-rc.42",
69
+ "@hot-updater/postgres": "1.0.0-rc.42"
70
70
  },
71
71
  "scripts": {
72
72
  "build": "tsdown",
@@ -104,6 +104,10 @@ CREATE INDEX IF NOT EXISTS "hot_updater_v1_api_keys_byCreated" ON "hot_updater_v
104
104
 
105
105
  CREATE UNIQUE INDEX IF NOT EXISTS "hot_updater_v1_api_keys_hash" ON "hot_updater_v1_api_keys" ("hash");
106
106
 
107
+ CREATE TABLE IF NOT EXISTS "hot_updater_v1_remote_config_active" ("id" varchar(16) COLLATE "C" NOT NULL, "version" bigint NOT NULL, "template" jsonb NOT NULL, "updated_at_ms" bigint NOT NULL, "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("id"));
108
+
109
+ CREATE TABLE IF NOT EXISTS "hot_updater_v1_remote_config_versions" ("version" bigint NOT NULL, "template" jsonb NOT NULL, "description" varchar(256) COLLATE "C", "update_type" varchar(16) COLLATE "C" NOT NULL, "rollback_source" bigint, "created_at_ms" bigint NOT NULL, "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("version"));
110
+
107
111
  CREATE TABLE IF NOT EXISTS "hot_updater_v1_private_hot_updater_settings" ("key" varchar(255) COLLATE "C" NOT NULL, "value" varchar(255) COLLATE "C" NOT NULL, "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("key"));
108
112
 
109
113
  ALTER TABLE "hot_updater_v1_bundles" ENABLE ROW LEVEL SECURITY;
@@ -148,6 +152,10 @@ ALTER TABLE "hot_updater_v1_insights_failures" ENABLE ROW LEVEL SECURITY;
148
152
 
149
153
  ALTER TABLE "hot_updater_v1_api_keys" ENABLE ROW LEVEL SECURITY;
150
154
 
155
+ ALTER TABLE "hot_updater_v1_remote_config_active" ENABLE ROW LEVEL SECURITY;
156
+
157
+ ALTER TABLE "hot_updater_v1_remote_config_versions" ENABLE ROW LEVEL SECURITY;
158
+
151
159
  ALTER TABLE "hot_updater_v1_private_hot_updater_settings" ENABLE ROW LEVEL SECURITY;
152
160
 
153
161
  ALTER TABLE "hot_updater_v1__hu_write" ENABLE ROW LEVEL SECURITY;
@@ -171,7 +179,7 @@ BEGIN
171
179
  FOR item IN SELECT value FROM jsonb_array_elements(p_statements) LOOP
172
180
  statement := item->>'sql';
173
181
  FOR target IN SELECT (regexp_matches(statement, '(?:FROM|INTO|UPDATE|JOIN) "([^"]+)"', 'g'))[1] LOOP
174
- IF NOT target = ANY (ARRAY['hot_updater_v1_bundles', 'hot_updater_v1_bundle_patches', 'hot_updater_v1_releases', 'hot_updater_v1_release_catalogs', 'hot_updater_v1_channels', 'hot_updater_v1_bundle_totals', 'hot_updater_v1_bundle_events', 'hot_updater_v1_bundle_events__byBundle', 'hot_updater_v1_bundle_event_heads', 'hot_updater_v1_insights_overview', 'hot_updater_v1_insights_sketches', 'hot_updater_v1_insights_overview_daily', 'hot_updater_v1_insights_sketches_daily', 'hot_updater_v1_insights_overview_lifetime', 'hot_updater_v1_insights_sketches_lifetime', 'hot_updater_v1_insights_distribution', 'hot_updater_v1_insights_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_private_hot_updater_settings', 'hot_updater_v1__hu_write']) THEN
182
+ IF NOT target = ANY (ARRAY['hot_updater_v1_bundles', 'hot_updater_v1_bundle_patches', 'hot_updater_v1_releases', 'hot_updater_v1_release_catalogs', 'hot_updater_v1_channels', 'hot_updater_v1_bundle_totals', 'hot_updater_v1_bundle_events', 'hot_updater_v1_bundle_events__byBundle', 'hot_updater_v1_bundle_event_heads', 'hot_updater_v1_insights_overview', 'hot_updater_v1_insights_sketches', 'hot_updater_v1_insights_overview_daily', 'hot_updater_v1_insights_sketches_daily', 'hot_updater_v1_insights_overview_lifetime', 'hot_updater_v1_insights_sketches_lifetime', 'hot_updater_v1_insights_distribution', 'hot_updater_v1_insights_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
175
183
  RAISE EXCEPTION 'hot_updater_v1_apply: % is not a Hot Updater table', target USING ERRCODE = '42501';
176
184
  END IF;
177
185
  END LOOP;
@@ -209,3 +217,5 @@ INSERT INTO "hot_updater_v1_private_hot_updater_settings" ("key", "value", "_v")
209
217
  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";
210
218
 
211
219
  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";
220
+
221
+ 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";