@supacloud/lite 0.8.1 → 0.8.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.3](https://github.com/zuohuadong/supacloud/compare/supacloud-lite-v0.8.2...supacloud-lite-v0.8.3) (2026-08-15)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **lite:** detect glibc in standalone runtime ([#925](https://github.com/zuohuadong/supacloud/issues/925)) ([c18f0f6](https://github.com/zuohuadong/supacloud/commit/c18f0f6ee86e488a7c603e13416e794060f2012d))
9
+
10
+ ## [0.8.2](https://github.com/zuohuadong/supacloud/compare/supacloud-lite-v0.8.1...supacloud-lite-v0.8.2) (2026-08-15)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **lite:** support mirrored native postgres downloads ([#916](https://github.com/zuohuadong/supacloud/issues/916)) ([48224b7](https://github.com/zuohuadong/supacloud/commit/48224b7b067fcc56b3be8059301525cebeefb98d))
16
+
3
17
  ## [0.8.1](https://github.com/zuohuadong/supacloud/compare/supacloud-lite-v0.8.0...supacloud-lite-v0.8.1) (2026-08-15)
4
18
 
5
19
 
package/README.md CHANGED
@@ -135,9 +135,16 @@ supacloud-lite version
135
135
  - `SUPACLOUD_LITE_STORAGE_DIR`
136
136
  - `SUPACLOUD_LITE_STORAGE_BACKEND`:`fs`(默认)、`memory` 或 `s3`
137
137
  - `SUPACLOUD_LITE_S3_PREFIX`:S3 对象 key 前缀,可被 `--s3-prefix` 覆盖
138
+ - `SUPACLOUD_LITE_POSTGRES_MIRROR`:native 引擎下载 PostgreSQL 发布包时使用的 HTTPS 镜像前缀,例如 `https://ghproxy.net/`
138
139
  - `SUPACLOUD_LITE_JWT_SECRET`
139
140
  - `SUPACLOUD_LITE_VAULT_KEY`
140
141
 
142
+ native 引擎首次运行会下载约 12 MB 的 Theseus PostgreSQL 发布包并缓存在 `~/.cache/supacloud-lite`。网络无法直连 GitHub 时,可设置镜像前缀;Lite 会把完整上游 URL 追加到该前缀,并在解压执行前继续校验固定或上游发布的 SHA-256。镜像不能包含 URL 凭据、query 或 fragment;除 loopback 测试地址外必须使用 HTTPS。
143
+
144
+ ```bash
145
+ SUPACLOUD_LITE_POSTGRES_MIRROR=https://ghproxy.net/ supacloud-lite migrate --engine native
146
+ ```
147
+
141
148
  使用远端 S3 时,启动前设置 `SUPACLOUD_LITE_STORAGE_BACKEND=s3`,并按 Bun S3 约定提供 `S3_BUCKET`、`S3_ACCESS_KEY_ID`、`S3_SECRET_ACCESS_KEY`、`S3_ENDPOINT`、`S3_REGION` 等变量;也支持对应的 `AWS_*` 变量。CLI 不接受密钥参数,避免凭据出现在进程列表中。
142
149
 
143
150
  S3 模式下 `db reset` 会被拒绝,因为 Lite 不能把数据库元数据清理与远端对象删除做成原子操作;需要先明确处理远端 bucket/prefix,再切回本地或内存存储执行重置。
@@ -243,7 +250,7 @@ S3 模式的快照只包含数据库中的 Storage 元数据和密钥,不复
243
250
  | `supabase.storage` | 已验证核心 | 覆盖上传下载、列表、删除、TUS/RLS、远端 S3 驱动,以及 Bun.Image 的 `contain`/`fill`、格式和质量变换子集;`cover` 明确不支持 |
244
251
  | `supabase.channel()` | 已验证核心 | 自动测试覆盖 `postgres_changes`、DELETE RLS 隔离和事件快照校验;Broadcast、Presence 属于实验性兼容 |
245
252
  | `supabase.functions.invoke()` | 已验证核心 | 自动测试覆盖 Bun.build、`Deno.serve()`、公开函数和同进程重启 |
246
- | Supabase Queues / PGMQ | 已验证核心 | 提供 `pgmq_public` 的 `send`、`send_batch`、`read`、`pop`、`archive`、`delete` RPC;队列数据持久化在同一 PGlite 项目中 |
253
+ | Supabase Queues / PGMQ | 已验证核心 | 提供 `pgmq_public` 的 `send`、`send_batch`、`read`、`pop`、`archive`、`delete` RPC;队列数据持久化在项目数据库(PGlite native PostgreSQL)中 |
247
254
  | Edge Functions `SupaCloud.pgredis` | 已验证核心 | 提供单项目持久 KV、TTL、原子 `getset`/`getdel`;绑定只在当前函数请求内可用 |
248
255
  | Supabase migrations | 支持 | 按文件名排序,记录到 `supabase_migrations` |
249
256
  | PostgreSQL RLS | 支持 | 使用 `anon`、`authenticated`、`service_role` 数据库角色执行 |
@@ -267,7 +274,7 @@ Lite 使用 Bun 原生 bcrypt,并兼容验证常见 GoTrue bcrypt 密码散列
267
274
 
268
275
  ### 队列与 Edge 缓存
269
276
 
270
- Lite 在同一个 PGlite 数据库中提供 Supabase Queues 的公开 RPC façade。应用可以直接使用官方客户端的
277
+ Lite 在项目数据库(PGlite native PostgreSQL)中提供 Supabase Queues 的公开 RPC façade。应用可以直接使用官方客户端的
271
278
  `supabase.schema('pgmq_public').rpc(...)`,无需额外的队列进程:
272
279
 
273
280
  ```sql
@@ -291,6 +298,7 @@ const { data: messages } = await queues.rpc('read', {
291
298
 
292
299
  Lite 的 `pgmq` 模拟层还提供 `set_vt`,支持直接 SQL 调整可见性超时。消息按 PGMQ 语义至少投递一次;`pop` 会立即删除消息,`archive` 是确认路径。队列创建、指标、purge、设置和管理 API 不属于 Lite 的公开 RPC façade,仍需由项目 SQL 或完整 SupaCloud 控制面处理。
293
300
  队列名遵循标准版的 1-128 位小写字母、数字、下划线和短横线规则,并且必须以字母或数字开头;Lite 会安全映射超出 PostgreSQL 63-byte 标识符上限的名称,避免截断后串队。默认 Data API 暴露 `public` 和 `pgmq_public`;如果显式设置 `dbSchemas`,Lite 会严格使用该列表,需要队列 RPC 时应把 `pgmq_public` 明确加入。
301
+ Durable workflow 的 run、step、event 和内部队列也保存在同一项目数据库中,可跨进程重启恢复;内部 workflow 队列不会通过 `pgmq_public` 暴露给 Data API 角色。
294
302
 
295
303
  Edge Function 内可使用 `globalThis.SupaCloud.pgredis`:
296
304
 
@@ -469,9 +477,16 @@ Environment variables:
469
477
  - `SUPACLOUD_LITE_STORAGE_DIR`
470
478
  - `SUPACLOUD_LITE_STORAGE_BACKEND`: `fs` (default), `memory`, or `s3`
471
479
  - `SUPACLOUD_LITE_S3_PREFIX`: S3 object key prefix, can be overridden by `--s3-prefix`
480
+ - `SUPACLOUD_LITE_POSTGRES_MIRROR`: HTTPS prefix used to proxy native-engine PostgreSQL release downloads, for example `https://ghproxy.net/`
472
481
  - `SUPACLOUD_LITE_JWT_SECRET`
473
482
  - `SUPACLOUD_LITE_VAULT_KEY`
474
483
 
484
+ On its first run, the native engine downloads an approximately 12 MB Theseus PostgreSQL release into `~/.cache/supacloud-lite`. If GitHub is not directly reachable, set a mirror prefix; Lite appends the complete upstream URL and still verifies the pinned or published SHA-256 before extracting or executing the archive. The mirror must not contain URL credentials, a query, or a fragment, and must use HTTPS except for loopback test endpoints.
485
+
486
+ ```bash
487
+ SUPACLOUD_LITE_POSTGRES_MIRROR=https://ghproxy.net/ supacloud-lite migrate --engine native
488
+ ```
489
+
475
490
  When using remote S3, set `SUPACLOUD_LITE_STORAGE_BACKEND=s3` before starting, and provide variables such as `S3_BUCKET`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `S3_ENDPOINT`, and `S3_REGION` according to Bun S3 conventions; the corresponding `AWS_*` variables are also supported. The CLI does not accept secret parameters to avoid credentials appearing in the process list.
476
491
 
477
492
  In S3 mode, `db reset` is rejected because Lite cannot make database metadata cleanup and remote object deletion an atomic operation; you must explicitly handle the remote bucket/prefix first, then switch back to local or memory storage to perform the reset.
@@ -577,7 +592,7 @@ In-memory databases have no persistable data, so `snapshot` and `upgrade` reject
577
592
  | `supabase.storage` | Verified core | Covers upload/download, list, delete, TUS/RLS, remote S3 driver, and a subset of Bun.Image `contain`/`fill`, format, and quality transforms; `cover` is explicitly unsupported |
578
593
  | `supabase.channel()` | Verified core | Automated tests cover `postgres_changes`, DELETE RLS isolation, and event snapshot validation; Broadcast and Presence are experimentally compatible |
579
594
  | `supabase.functions.invoke()` | Verified core | Automated tests cover Bun.build, `Deno.serve()`, public functions, and in-process restart |
580
- | Supabase Queues / PGMQ | Verified core | Provides `send`, `send_batch`, `read`, `pop`, `archive`, and `delete` RPCs for `pgmq_public`; queue data is persisted in the same PGlite project |
595
+ | Supabase Queues / PGMQ | Verified core | Provides `send`, `send_batch`, `read`, `pop`, `archive`, and `delete` RPCs for `pgmq_public`; queue data is persisted in the project database (PGlite or native PostgreSQL) |
581
596
  | Edge Functions `SupaCloud.pgredis` | Verified core | Provides single-project persistent KV, TTL, and atomic `getset`/`getdel`; the binding is only available within the current function request |
582
597
  | Supabase migrations | Supported | Sorted by filename, recorded in `supabase_migrations` |
583
598
  | PostgreSQL RLS | Supported | Executed using the `anon`, `authenticated`, and `service_role` database roles |
@@ -601,7 +616,7 @@ Lite uses Bun's native bcrypt and is compatible with validating common GoTrue bc
601
616
 
602
617
  ### Queues and Edge Cache
603
618
 
604
- Lite provides a public RPC façade for Supabase Queues within the same PGlite database. Applications can directly use the official client's
619
+ Lite provides a public RPC façade for Supabase Queues in the project database (PGlite or native PostgreSQL). Applications can directly use the official client's
605
620
  `supabase.schema('pgmq_public').rpc(...)` without an additional queue process:
606
621
 
607
622
  ```sql
@@ -625,6 +640,7 @@ const { data: messages } = await queues.rpc('read', {
625
640
 
626
641
  Lite's `pgmq` emulation layer also provides `set_vt`, allowing direct SQL adjustment of visibility timeouts. Messages are delivered at least once according to PGMQ semantics; `pop` deletes the message immediately, and `archive` is the acknowledgment path. Queue creation, metrics, purge, settings, and management APIs are not part of Lite's public RPC façade and must still be handled by project SQL or the full SupaCloud control plane.
627
642
  Queue names follow the standard 1-128 character lowercase letter, digit, underscore, and hyphen rule, and must start with a letter or digit; Lite safely maps names that exceed PostgreSQL's 63-byte identifier limit to avoid cross-queue collisions after truncation. The default Data API exposes `public` and `pgmq_public`; if `dbSchemas` is set explicitly, Lite uses that list strictly, and `pgmq_public` should be explicitly included when queue RPCs are needed.
643
+ Durable workflow runs, steps, events, and the internal queue are stored in the same project database and survive process restarts. Data API roles cannot access the internal workflow queue through `pgmq_public`.
628
644
 
629
645
  Within an Edge Function, you can use `globalThis.SupaCloud.pgredis`:
630
646
 
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import { dirname as dirname7, join as join10, resolve as resolve4 } from "path";
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "@supacloud/lite",
11
- version: "0.8.1",
11
+ version: "0.8.3",
12
12
  description: "Bun-native, single-project Supabase-compatible backend powered by PGlite or native PostgreSQL",
13
13
  type: "module",
14
14
  license: "Apache-2.0",
@@ -45,7 +45,7 @@ var package_default = {
45
45
  dev: "bun run src/cli.ts start",
46
46
  start: "bun run src/cli.ts start",
47
47
  test: "bun test --timeout 20000",
48
- "test:native": "SUPACLOUD_LITE_TEST_NATIVE=1 bun test test/native-engine.test.ts --timeout 180000",
48
+ "test:native": "SUPACLOUD_LITE_TEST_NATIVE=1 bun test test/native-engine.test.ts test/qauth-workflow-matrix.test.ts --timeout 180000",
49
49
  parity: "bun run parity/harness.ts",
50
50
  "parity:native": "SUPACLOUD_LITE_TEST_NATIVE=1 bun run parity/harness.ts --engine native",
51
51
  "check:native": "bun run test:native && bun run parity:native",
@@ -10256,7 +10256,7 @@ import { tmpdir as tmpdir2 } from "os";
10256
10256
  import { dirname as dirname2, join as join2 } from "path";
10257
10257
 
10258
10258
  // src/runtime/node/native/engine.ts
10259
- import { execFileSync, spawn } from "child_process";
10259
+ import { execFileSync, spawn, spawnSync } from "child_process";
10260
10260
  import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
10261
10261
  import { appendFileSync, chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync } from "fs";
10262
10262
  import { writeFile } from "fs/promises";
@@ -10796,17 +10796,47 @@ function toPgArrayLiteral(array) {
10796
10796
 
10797
10797
  // src/runtime/node/native/engine.ts
10798
10798
  var DEFAULT_PG_VERSION = "17.7.0";
10799
+ var POSTGRES_MIRROR_URL_ERROR = "SUPACLOUD_LITE_POSTGRES_MIRROR must be an absolute HTTPS URL or a loopback HTTP URL";
10799
10800
  var NATIVE_POSTGRES_MAJOR = DEFAULT_PG_VERSION.split(".")[0];
10801
+ var GLIBC_DYNAMIC_LOADERS = {
10802
+ x64: [
10803
+ "/lib64/ld-linux-x86-64.so.2",
10804
+ "/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2"
10805
+ ],
10806
+ arm64: [
10807
+ "/lib/ld-linux-aarch64.so.1",
10808
+ "/lib64/ld-linux-aarch64.so.1",
10809
+ "/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1"
10810
+ ]
10811
+ };
10800
10812
  function isNativeEngineSupported() {
10801
10813
  return (process.platform === "darwin" || process.platform === "linux") && (process.arch === "arm64" || process.arch === "x64") && (process.platform !== "linux" || isGlibcLinux());
10802
10814
  }
10803
10815
  function isGlibcLinux() {
10804
- try {
10805
- const version = execFileSync("ldd", ["--version"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
10806
- return /glibc|gnu libc/i.test(version);
10807
- } catch {
10808
- return false;
10809
- }
10816
+ const runtimeEvidence = {
10817
+ runtimeVersion: reportedGlibcVersion(),
10818
+ dynamicLoaderPresent: glibcDynamicLoaderPresent()
10819
+ };
10820
+ return isGlibcRuntime(runtimeEvidence) || isGlibcRuntime({ ...runtimeEvidence, lddVersion: lddVersion() });
10821
+ }
10822
+ function isGlibcRuntime(evidence) {
10823
+ return Boolean(evidence.runtimeVersion?.trim()) || evidence.dynamicLoaderPresent || /glibc|gnu libc/i.test(evidence.lddVersion ?? "");
10824
+ }
10825
+ function reportedGlibcVersion() {
10826
+ const report = process.report?.getReport();
10827
+ const runtimeVersion = report?.header?.glibcVersionRuntime;
10828
+ return typeof runtimeVersion === "string" ? runtimeVersion : undefined;
10829
+ }
10830
+ function glibcDynamicLoaderPresent() {
10831
+ const loaderPaths = process.arch === "x64" ? GLIBC_DYNAMIC_LOADERS.x64 : process.arch === "arm64" ? GLIBC_DYNAMIC_LOADERS.arm64 : [];
10832
+ return loaderPaths.some((loaderPath) => existsSync(loaderPath));
10833
+ }
10834
+ function lddVersion() {
10835
+ const command = spawnSync("ldd", ["--version"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
10836
+ if (command.error || command.status !== 0)
10837
+ return;
10838
+ return `${command.stdout}
10839
+ ${command.stderr}`.trim();
10810
10840
  }
10811
10841
  function target() {
10812
10842
  const arch = process.arch === "arm64" ? "aarch64" : process.arch === "x64" ? "x86_64" : null;
@@ -10827,7 +10857,7 @@ var PINNED_SHA256 = {
10827
10857
  "postgresql-17.7.0-x86_64-apple-darwin": "0dd8c25173524bad4ae8ef6b970da1ac40f4c1f231150c416ccb8cd06feff8f2",
10828
10858
  "postgresql-17.7.0-aarch64-apple-darwin": "727ac08d20a704014a0d51eb3300aa0c8e292c1cf0a1c99d4f4b1002e1420220"
10829
10859
  };
10830
- async function verifyTarball(tarball, key, url) {
10860
+ async function verifyTarball(tarball, key, upstreamUrl, downloadMirror) {
10831
10861
  const actual = createHash2("sha256").update(readFileSync(tarball)).digest("hex");
10832
10862
  const pinned = PINNED_SHA256[key];
10833
10863
  if (pinned) {
@@ -10836,7 +10866,8 @@ async function verifyTarball(tarball, key, url) {
10836
10866
  }
10837
10867
  return;
10838
10868
  }
10839
- const res = await fetchRelease(`${url}.sha256`);
10869
+ const checksumUrl = resolvePostgresDownloadUrl(`${upstreamUrl}.sha256`, downloadMirror);
10870
+ const res = await fetchRelease(checksumUrl);
10840
10871
  if (!res.ok)
10841
10872
  throw new Error(`could not fetch checksum for ${key}: HTTP ${res.status}`);
10842
10873
  const expected = (await res.text()).trim().split(/\s+/)[0].toLowerCase();
@@ -10846,13 +10877,14 @@ async function verifyTarball(tarball, key, url) {
10846
10877
  throw new Error(`postgres binary checksum mismatch for ${key}: expected ${expected}, got ${actual}`);
10847
10878
  }
10848
10879
  }
10849
- async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log) {
10880
+ async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log, downloadMirror) {
10850
10881
  const t = target();
10851
10882
  const root = cacheDir ?? join(homedir(), ".cache", "supacloud-lite");
10852
10883
  const dir = join(root, `postgresql-${version}-${t}`);
10853
10884
  if (isCompleteInstall(dir))
10854
10885
  return dir;
10855
- const url = `https://github.com/theseus-rs/postgresql-binaries/releases/download/${version}/postgresql-${version}-${t}.tar.gz`;
10886
+ const upstreamUrl = `https://github.com/theseus-rs/postgresql-binaries/releases/download/${version}/postgresql-${version}-${t}.tar.gz`;
10887
+ const url = resolvePostgresDownloadUrl(upstreamUrl, downloadMirror);
10856
10888
  mkdirSync(root, { recursive: true });
10857
10889
  const uniq = `${process.pid}-${randomBytes2(6).toString("hex")}`;
10858
10890
  const tarball = join(root, `pg-${version}-${uniq}.tar.gz`);
@@ -10865,7 +10897,7 @@ async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log) {
10865
10897
  if (!res.ok)
10866
10898
  throw new Error(`failed to download ${url}: HTTP ${res.status}`);
10867
10899
  await writeFile(tarball, Buffer.from(await res.arrayBuffer()));
10868
- await verifyTarball(tarball, `postgresql-${version}-${t}`, url);
10900
+ await verifyTarball(tarball, `postgresql-${version}-${t}`, upstreamUrl, downloadMirror);
10869
10901
  mkdirSync(tmpDir, { recursive: true });
10870
10902
  await extractTar({ cwd: tmpDir, file: tarball, gzip: true, preserveOwner: false, strict: true, strip: 1 });
10871
10903
  if (!isCompleteInstall(tmpDir))
@@ -10885,6 +10917,32 @@ async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log) {
10885
10917
  rmSync(tmpDir, { recursive: true, force: true });
10886
10918
  }
10887
10919
  }
10920
+ function resolvePostgresDownloadUrl(upstreamUrl, downloadMirror) {
10921
+ const mirror = postgresDownloadMirror(downloadMirror);
10922
+ return mirror ? `${mirror.toString().replace(/\/+$/, "")}/${upstreamUrl}` : upstreamUrl;
10923
+ }
10924
+ function postgresDownloadMirror(downloadMirror) {
10925
+ const configuredMirror = downloadMirror ?? process.env.SUPACLOUD_LITE_POSTGRES_MIRROR;
10926
+ const mirrorText = configuredMirror?.trim();
10927
+ if (!mirrorText)
10928
+ return;
10929
+ let mirror;
10930
+ try {
10931
+ mirror = new URL(mirrorText);
10932
+ } catch {
10933
+ throw new Error(POSTGRES_MIRROR_URL_ERROR);
10934
+ }
10935
+ const loopbackHttp = mirror.protocol === "http:" && (mirror.hostname === "127.0.0.1" || mirror.hostname === "localhost" || mirror.hostname === "[::1]");
10936
+ if (mirror.protocol !== "https:" && !loopbackHttp)
10937
+ throw new Error(POSTGRES_MIRROR_URL_ERROR);
10938
+ if (mirror.username || mirror.password) {
10939
+ throw new Error("SUPACLOUD_LITE_POSTGRES_MIRROR must not contain credentials");
10940
+ }
10941
+ if (mirrorText.includes("?") || mirrorText.includes("#")) {
10942
+ throw new Error("SUPACLOUD_LITE_POSTGRES_MIRROR must not contain a query or fragment");
10943
+ }
10944
+ return mirror;
10945
+ }
10888
10946
  async function fetchRelease(url) {
10889
10947
  let lastError;
10890
10948
  for (let attempt = 1;attempt <= 3; attempt++) {
@@ -10917,7 +10975,7 @@ async function createNativeEngine(opts) {
10917
10975
  let postgres;
10918
10976
  let removeExitHandler;
10919
10977
  try {
10920
- const installDir = await ensurePostgres(opts.version, opts.cacheDir, opts.log);
10978
+ const installDir = await ensurePostgres(opts.version, opts.cacheDir, opts.log, opts.downloadMirror);
10921
10979
  const bin = (name) => join(installDir, "bin", name);
10922
10980
  if (!existsSync(join(opts.dataDir, "PG_VERSION"))) {
10923
10981
  mkdirSync(opts.dataDir, { recursive: true });
package/dist/index.js CHANGED
@@ -85,7 +85,7 @@ function randomToken(bytes = 32) {
85
85
  // package.json
86
86
  var package_default = {
87
87
  name: "@supacloud/lite",
88
- version: "0.8.1",
88
+ version: "0.8.3",
89
89
  description: "Bun-native, single-project Supabase-compatible backend powered by PGlite or native PostgreSQL",
90
90
  type: "module",
91
91
  license: "Apache-2.0",
@@ -122,7 +122,7 @@ var package_default = {
122
122
  dev: "bun run src/cli.ts start",
123
123
  start: "bun run src/cli.ts start",
124
124
  test: "bun test --timeout 20000",
125
- "test:native": "SUPACLOUD_LITE_TEST_NATIVE=1 bun test test/native-engine.test.ts --timeout 180000",
125
+ "test:native": "SUPACLOUD_LITE_TEST_NATIVE=1 bun test test/native-engine.test.ts test/qauth-workflow-matrix.test.ts --timeout 180000",
126
126
  parity: "bun run parity/harness.ts",
127
127
  "parity:native": "SUPACLOUD_LITE_TEST_NATIVE=1 bun run parity/harness.ts --engine native",
128
128
  "check:native": "bun run test:native && bun run parity:native",
@@ -10269,7 +10269,7 @@ async function serveBun(backend, opts = {}) {
10269
10269
  };
10270
10270
  }
10271
10271
  // src/runtime/node/native/engine.ts
10272
- import { execFileSync, spawn } from "child_process";
10272
+ import { execFileSync, spawn, spawnSync } from "child_process";
10273
10273
  import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
10274
10274
  import { appendFileSync, chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync } from "fs";
10275
10275
  import { writeFile as writeFile2 } from "fs/promises";
@@ -10809,17 +10809,47 @@ function toPgArrayLiteral(array) {
10809
10809
 
10810
10810
  // src/runtime/node/native/engine.ts
10811
10811
  var DEFAULT_PG_VERSION = "17.7.0";
10812
+ var POSTGRES_MIRROR_URL_ERROR = "SUPACLOUD_LITE_POSTGRES_MIRROR must be an absolute HTTPS URL or a loopback HTTP URL";
10812
10813
  var NATIVE_POSTGRES_MAJOR = DEFAULT_PG_VERSION.split(".")[0];
10814
+ var GLIBC_DYNAMIC_LOADERS = {
10815
+ x64: [
10816
+ "/lib64/ld-linux-x86-64.so.2",
10817
+ "/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2"
10818
+ ],
10819
+ arm64: [
10820
+ "/lib/ld-linux-aarch64.so.1",
10821
+ "/lib64/ld-linux-aarch64.so.1",
10822
+ "/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1"
10823
+ ]
10824
+ };
10813
10825
  function isNativeEngineSupported() {
10814
10826
  return (process.platform === "darwin" || process.platform === "linux") && (process.arch === "arm64" || process.arch === "x64") && (process.platform !== "linux" || isGlibcLinux());
10815
10827
  }
10816
10828
  function isGlibcLinux() {
10817
- try {
10818
- const version = execFileSync("ldd", ["--version"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
10819
- return /glibc|gnu libc/i.test(version);
10820
- } catch {
10821
- return false;
10822
- }
10829
+ const runtimeEvidence = {
10830
+ runtimeVersion: reportedGlibcVersion(),
10831
+ dynamicLoaderPresent: glibcDynamicLoaderPresent()
10832
+ };
10833
+ return isGlibcRuntime(runtimeEvidence) || isGlibcRuntime({ ...runtimeEvidence, lddVersion: lddVersion() });
10834
+ }
10835
+ function isGlibcRuntime(evidence) {
10836
+ return Boolean(evidence.runtimeVersion?.trim()) || evidence.dynamicLoaderPresent || /glibc|gnu libc/i.test(evidence.lddVersion ?? "");
10837
+ }
10838
+ function reportedGlibcVersion() {
10839
+ const report = process.report?.getReport();
10840
+ const runtimeVersion = report?.header?.glibcVersionRuntime;
10841
+ return typeof runtimeVersion === "string" ? runtimeVersion : undefined;
10842
+ }
10843
+ function glibcDynamicLoaderPresent() {
10844
+ const loaderPaths = process.arch === "x64" ? GLIBC_DYNAMIC_LOADERS.x64 : process.arch === "arm64" ? GLIBC_DYNAMIC_LOADERS.arm64 : [];
10845
+ return loaderPaths.some((loaderPath) => existsSync(loaderPath));
10846
+ }
10847
+ function lddVersion() {
10848
+ const command = spawnSync("ldd", ["--version"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
10849
+ if (command.error || command.status !== 0)
10850
+ return;
10851
+ return `${command.stdout}
10852
+ ${command.stderr}`.trim();
10823
10853
  }
10824
10854
  function target() {
10825
10855
  const arch = process.arch === "arm64" ? "aarch64" : process.arch === "x64" ? "x86_64" : null;
@@ -10840,7 +10870,7 @@ var PINNED_SHA256 = {
10840
10870
  "postgresql-17.7.0-x86_64-apple-darwin": "0dd8c25173524bad4ae8ef6b970da1ac40f4c1f231150c416ccb8cd06feff8f2",
10841
10871
  "postgresql-17.7.0-aarch64-apple-darwin": "727ac08d20a704014a0d51eb3300aa0c8e292c1cf0a1c99d4f4b1002e1420220"
10842
10872
  };
10843
- async function verifyTarball(tarball, key, url) {
10873
+ async function verifyTarball(tarball, key, upstreamUrl, downloadMirror) {
10844
10874
  const actual = createHash2("sha256").update(readFileSync(tarball)).digest("hex");
10845
10875
  const pinned = PINNED_SHA256[key];
10846
10876
  if (pinned) {
@@ -10849,7 +10879,8 @@ async function verifyTarball(tarball, key, url) {
10849
10879
  }
10850
10880
  return;
10851
10881
  }
10852
- const res = await fetchRelease(`${url}.sha256`);
10882
+ const checksumUrl = resolvePostgresDownloadUrl(`${upstreamUrl}.sha256`, downloadMirror);
10883
+ const res = await fetchRelease(checksumUrl);
10853
10884
  if (!res.ok)
10854
10885
  throw new Error(`could not fetch checksum for ${key}: HTTP ${res.status}`);
10855
10886
  const expected = (await res.text()).trim().split(/\s+/)[0].toLowerCase();
@@ -10859,13 +10890,14 @@ async function verifyTarball(tarball, key, url) {
10859
10890
  throw new Error(`postgres binary checksum mismatch for ${key}: expected ${expected}, got ${actual}`);
10860
10891
  }
10861
10892
  }
10862
- async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log) {
10893
+ async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log, downloadMirror) {
10863
10894
  const t = target();
10864
10895
  const root = cacheDir ?? join2(homedir(), ".cache", "supacloud-lite");
10865
10896
  const dir = join2(root, `postgresql-${version}-${t}`);
10866
10897
  if (isCompleteInstall(dir))
10867
10898
  return dir;
10868
- const url = `https://github.com/theseus-rs/postgresql-binaries/releases/download/${version}/postgresql-${version}-${t}.tar.gz`;
10899
+ const upstreamUrl = `https://github.com/theseus-rs/postgresql-binaries/releases/download/${version}/postgresql-${version}-${t}.tar.gz`;
10900
+ const url = resolvePostgresDownloadUrl(upstreamUrl, downloadMirror);
10869
10901
  mkdirSync(root, { recursive: true });
10870
10902
  const uniq = `${process.pid}-${randomBytes2(6).toString("hex")}`;
10871
10903
  const tarball = join2(root, `pg-${version}-${uniq}.tar.gz`);
@@ -10878,7 +10910,7 @@ async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log) {
10878
10910
  if (!res.ok)
10879
10911
  throw new Error(`failed to download ${url}: HTTP ${res.status}`);
10880
10912
  await writeFile2(tarball, Buffer.from(await res.arrayBuffer()));
10881
- await verifyTarball(tarball, `postgresql-${version}-${t}`, url);
10913
+ await verifyTarball(tarball, `postgresql-${version}-${t}`, upstreamUrl, downloadMirror);
10882
10914
  mkdirSync(tmpDir, { recursive: true });
10883
10915
  await extractTar({ cwd: tmpDir, file: tarball, gzip: true, preserveOwner: false, strict: true, strip: 1 });
10884
10916
  if (!isCompleteInstall(tmpDir))
@@ -10898,6 +10930,32 @@ async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log) {
10898
10930
  rmSync(tmpDir, { recursive: true, force: true });
10899
10931
  }
10900
10932
  }
10933
+ function resolvePostgresDownloadUrl(upstreamUrl, downloadMirror) {
10934
+ const mirror = postgresDownloadMirror(downloadMirror);
10935
+ return mirror ? `${mirror.toString().replace(/\/+$/, "")}/${upstreamUrl}` : upstreamUrl;
10936
+ }
10937
+ function postgresDownloadMirror(downloadMirror) {
10938
+ const configuredMirror = downloadMirror ?? process.env.SUPACLOUD_LITE_POSTGRES_MIRROR;
10939
+ const mirrorText = configuredMirror?.trim();
10940
+ if (!mirrorText)
10941
+ return;
10942
+ let mirror;
10943
+ try {
10944
+ mirror = new URL(mirrorText);
10945
+ } catch {
10946
+ throw new Error(POSTGRES_MIRROR_URL_ERROR);
10947
+ }
10948
+ const loopbackHttp = mirror.protocol === "http:" && (mirror.hostname === "127.0.0.1" || mirror.hostname === "localhost" || mirror.hostname === "[::1]");
10949
+ if (mirror.protocol !== "https:" && !loopbackHttp)
10950
+ throw new Error(POSTGRES_MIRROR_URL_ERROR);
10951
+ if (mirror.username || mirror.password) {
10952
+ throw new Error("SUPACLOUD_LITE_POSTGRES_MIRROR must not contain credentials");
10953
+ }
10954
+ if (mirrorText.includes("?") || mirrorText.includes("#")) {
10955
+ throw new Error("SUPACLOUD_LITE_POSTGRES_MIRROR must not contain a query or fragment");
10956
+ }
10957
+ return mirror;
10958
+ }
10901
10959
  async function fetchRelease(url) {
10902
10960
  let lastError;
10903
10961
  for (let attempt = 1;attempt <= 3; attempt++) {
@@ -10930,7 +10988,7 @@ async function createNativeEngine(opts) {
10930
10988
  let postgres;
10931
10989
  let removeExitHandler;
10932
10990
  try {
10933
- const installDir = await ensurePostgres(opts.version, opts.cacheDir, opts.log);
10991
+ const installDir = await ensurePostgres(opts.version, opts.cacheDir, opts.log, opts.downloadMirror);
10934
10992
  const bin = (name) => join2(installDir, "bin", name);
10935
10993
  if (!existsSync(join2(opts.dataDir, "PG_VERSION"))) {
10936
10994
  mkdirSync(opts.dataDir, { recursive: true });
@@ -1,5 +1,10 @@
1
1
  import type { DbEngine } from '../../db/engine.js';
2
2
  export declare const NATIVE_POSTGRES_MAJOR: string;
3
+ export interface GlibcRuntimeEvidence {
4
+ runtimeVersion?: string;
5
+ dynamicLoaderPresent: boolean;
6
+ lddVersion?: string;
7
+ }
3
8
  /** Options for {@link createNativeEngine}. */
4
9
  export interface NativeEngineOptions {
5
10
  /** Postgres data directory (created + initdb'd if missing). */
@@ -10,10 +15,18 @@ export interface NativeEngineOptions {
10
15
  cacheDir?: string;
11
16
  /** sink for progress lines (download, install); no-op when omitted */
12
17
  log?: (msg: string) => void;
18
+ /** Optional HTTPS or loopback HTTP prefix for proxying the PostgreSQL release download. */
19
+ downloadMirror?: string;
13
20
  }
14
21
  export declare function isNativeEngineSupported(): boolean;
22
+ export declare function isGlibcRuntime(evidence: GlibcRuntimeEvidence): boolean;
15
23
  /** Download + unpack Postgres binaries if not already cached (concurrency-safe). Returns the install dir. */
16
- export declare function ensurePostgres(version?: string, cacheDir?: string, log?: (m: string) => void): Promise<string>;
24
+ export declare function ensurePostgres(version?: string, cacheDir?: string, log?: (m: string) => void, downloadMirror?: string): Promise<string>;
25
+ /**
26
+ * Prefix a trusted GitHub release URL with an operator-selected mirror. The
27
+ * archive is still checked against the pinned or published SHA-256 before use.
28
+ */
29
+ export declare function resolvePostgresDownloadUrl(upstreamUrl: string, downloadMirror?: string): string;
17
30
  /**
18
31
  * Boot the embedded Postgres child (initdb on first run) and return a {@link DbEngine}
19
32
  * backed by two wire connections: one for queries/transactions (serialized by a mutex),
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../../src/runtime/node/native/engine.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAKlD,eAAO,MAAM,qBAAqB,QAAoC,CAAA;AAEtE,8CAA8C;AAC9C,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAA;IACf,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,sEAAsE;IACtE,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAC5B;AAED,wBAAgB,uBAAuB,IAAI,OAAO,CAIjD;AA4DD,6GAA6G;AAC7G,wBAAsB,cAAc,CAAC,OAAO,SAAqB,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CA2ChI;AA4BD;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAiFrF"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../../src/runtime/node/native/engine.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAOlD,eAAO,MAAM,qBAAqB,QAAoC,CAAA;AActE,MAAM,WAAW,oBAAoB;IACnC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,oBAAoB,EAAE,OAAO,CAAA;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,8CAA8C;AAC9C,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAA;IACf,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,sEAAsE;IACtE,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3B,2FAA2F;IAC3F,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,wBAAgB,uBAAuB,IAAI,OAAO,CAIjD;AAUD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,oBAAoB,GAAG,OAAO,CAItE;AAuED,6GAA6G;AAC7G,wBAAsB,cAAc,CAClC,OAAO,SAAqB,EAC5B,QAAQ,CAAC,EAAE,MAAM,EACjB,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,EACzB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,MAAM,CAAC,CA4CjB;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,CAG/F;AAkDD;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAiFrF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/lite",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
4
4
  "description": "Bun-native, single-project Supabase-compatible backend powered by PGlite or native PostgreSQL",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -37,7 +37,7 @@
37
37
  "dev": "bun run src/cli.ts start",
38
38
  "start": "bun run src/cli.ts start",
39
39
  "test": "bun test --timeout 20000",
40
- "test:native": "SUPACLOUD_LITE_TEST_NATIVE=1 bun test test/native-engine.test.ts --timeout 180000",
40
+ "test:native": "SUPACLOUD_LITE_TEST_NATIVE=1 bun test test/native-engine.test.ts test/qauth-workflow-matrix.test.ts --timeout 180000",
41
41
  "parity": "bun run parity/harness.ts",
42
42
  "parity:native": "SUPACLOUD_LITE_TEST_NATIVE=1 bun run parity/harness.ts --engine native",
43
43
  "check:native": "bun run test:native && bun run parity:native",