@supacloud/lite 0.8.0 → 0.8.2

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.2](https://github.com/zuohuadong/supacloud/compare/supacloud-lite-v0.8.1...supacloud-lite-v0.8.2) (2026-08-15)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **lite:** support mirrored native postgres downloads ([#916](https://github.com/zuohuadong/supacloud/issues/916)) ([48224b7](https://github.com/zuohuadong/supacloud/commit/48224b7b067fcc56b3be8059301525cebeefb98d))
9
+
10
+ ## [0.8.1](https://github.com/zuohuadong/supacloud/compare/supacloud-lite-v0.8.0...supacloud-lite-v0.8.1) (2026-08-15)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **lite:** complete extended auth compatibility ([#912](https://github.com/zuohuadong/supacloud/issues/912)) ([dd657f5](https://github.com/zuohuadong/supacloud/commit/dd657f5b52f2f3d65c0178f76fd78d027c153c5a))
16
+
3
17
  ## [0.8.0](https://github.com/zuohuadong/supacloud/compare/supacloud-lite-v0.7.3...supacloud-lite-v0.8.0) (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,再切回本地或内存存储执行重置。
@@ -469,9 +476,16 @@ Environment variables:
469
476
  - `SUPACLOUD_LITE_STORAGE_DIR`
470
477
  - `SUPACLOUD_LITE_STORAGE_BACKEND`: `fs` (default), `memory`, or `s3`
471
478
  - `SUPACLOUD_LITE_S3_PREFIX`: S3 object key prefix, can be overridden by `--s3-prefix`
479
+ - `SUPACLOUD_LITE_POSTGRES_MIRROR`: HTTPS prefix used to proxy native-engine PostgreSQL release downloads, for example `https://ghproxy.net/`
472
480
  - `SUPACLOUD_LITE_JWT_SECRET`
473
481
  - `SUPACLOUD_LITE_VAULT_KEY`
474
482
 
483
+ 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.
484
+
485
+ ```bash
486
+ SUPACLOUD_LITE_POSTGRES_MIRROR=https://ghproxy.net/ supacloud-lite migrate --engine native
487
+ ```
488
+
475
489
  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
490
 
477
491
  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.
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.0",
11
+ version: "0.8.2",
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",
@@ -1296,7 +1296,8 @@ class AuthHandler {
1296
1296
  return await this.oauth.authorize(url);
1297
1297
  }
1298
1298
  if (path === "callback" && (method === "GET" || method === "POST")) {
1299
- return await this.oauth.callback(url, (userId) => this.sessionTokensFor(userId));
1299
+ const callbackUrl = method === "POST" ? await this.oauthCallbackUrl(req, url) : url;
1300
+ return await this.oauth.callback(callbackUrl, (userId) => this.oauthSessionTokensFor(userId));
1300
1301
  }
1301
1302
  if (path.startsWith("admin/"))
1302
1303
  return await this.admin(req, ctx, path, method);
@@ -1306,6 +1307,16 @@ class AuthHandler {
1306
1307
  return authError(500, "unexpected_failure", msg);
1307
1308
  }
1308
1309
  }
1310
+ async oauthCallbackUrl(req, url) {
1311
+ const callbackUrl = new URL(url);
1312
+ const form = await req.formData();
1313
+ for (const field of ["code", "state"]) {
1314
+ const value = form.get(field);
1315
+ if (typeof value === "string" && value)
1316
+ callbackUrl.searchParams.set(field, value);
1317
+ }
1318
+ return callbackUrl;
1319
+ }
1309
1320
  async signup(req) {
1310
1321
  const body = await req.json().catch(() => ({}));
1311
1322
  if (!body.email && !body.password) {
@@ -1383,7 +1394,7 @@ class AuthHandler {
1383
1394
  if (!userId)
1384
1395
  return authError(403, "flow_state_not_found", "invalid or expired auth code");
1385
1396
  const ures = await this.db.query(`select * from auth.users where id = $1`, [userId]);
1386
- return json(200, await this.sessionFor(ures.rows[0]));
1397
+ return json(200, await this.sessionForOAuth(ures.rows[0]));
1387
1398
  }
1388
1399
  return authError(400, "invalid_grant", `unsupported grant_type: ${grantType}`);
1389
1400
  }
@@ -1428,17 +1439,18 @@ class AuthHandler {
1428
1439
  sets.push(`is_anonymous = false`);
1429
1440
  sets.push(`raw_app_meta_data = coalesce(raw_app_meta_data, '{}'::jsonb) || '{"provider":"email","providers":["email"]}'::jsonb`);
1430
1441
  }
1431
- if (sets.length === 0)
1432
- return json(200, this.userJson(user));
1433
- params.push(user.id);
1434
- const res = await this.db.query(`update auth.users set ${sets.join(", ")}, updated_at = now() where id = $${params.length} returning *`, params);
1435
- const updated = res.rows[0];
1436
- if (upgradingAnon) {
1437
- await this.db.query(`insert into auth.identities (user_id, provider, provider_id, identity_data)
1438
- values ($1, 'email', $2, $3)
1439
- on conflict (provider, provider_id) do nothing`, [updated.id, updated.id, JSON.stringify({ sub: updated.id, email: updated.email })]);
1442
+ if (sets.length === 0) {
1443
+ return json(200, this.userJson(user, await this.getUserFactors(user.id), await this.getUserIdentities(user.id)));
1440
1444
  }
1441
- return json(200, this.userJson(updated));
1445
+ params.push(user.id);
1446
+ const payload = await this.db.transaction(async (query) => {
1447
+ const res = await query(`update auth.users set ${sets.join(", ")}, updated_at = now() where id = $${params.length} returning *`, params);
1448
+ const updated = res.rows[0];
1449
+ if (body.email)
1450
+ await this.ensureEmailIdentity(updated, query);
1451
+ return this.userJsonWithRelations(updated, query);
1452
+ });
1453
+ return json(200, payload);
1442
1454
  }
1443
1455
  async logout(req, url) {
1444
1456
  const claims = await this.claimsFromBearer(req);
@@ -1468,7 +1480,7 @@ class AuthHandler {
1468
1480
  let user = res.rows[0];
1469
1481
  if (!user) {
1470
1482
  if (!createUser)
1471
- return authError(422, "otp_disabled", "Signups not allowed for otp");
1483
+ return json(200, {});
1472
1484
  if (this.settings.disableSignup)
1473
1485
  return authError(422, "signup_disabled", "Signups not allowed for this instance");
1474
1486
  res = await this.db.query(`insert into auth.users (aud, role, email, raw_app_meta_data, raw_user_meta_data)
@@ -1800,6 +1812,8 @@ Or sign in with this link: ${link}`
1800
1812
  }
1801
1813
  const idMatch = path.match(/^admin\/users\/([0-9a-f-]{36})$/);
1802
1814
  const exportMatch = path.match(/^admin\/users\/([0-9a-f-]{36})\/export$/);
1815
+ const factorsMatch = path.match(/^admin\/users\/([0-9a-f-]{36})\/factors$/);
1816
+ const factorMatch = path.match(/^admin\/users\/([0-9a-f-]{36})\/factors\/([0-9a-f-]{36})$/);
1803
1817
  if (path === "admin/generate_link" && method === "POST") {
1804
1818
  return await this.generateAdminMagicLink(req);
1805
1819
  }
@@ -1811,32 +1825,49 @@ Or sign in with this link: ${link}`
1811
1825
  if (exportMatch && method === "GET") {
1812
1826
  return await this.exportUser(exportMatch[1]);
1813
1827
  }
1828
+ if (factorsMatch && method === "GET") {
1829
+ const user = await this.db.query(`select 1 from auth.users where id = $1`, [factorsMatch[1]]);
1830
+ if (user.rows.length === 0)
1831
+ return authError(404, "user_not_found", "User not found");
1832
+ return json(200, await this.getUserFactors(factorsMatch[1]));
1833
+ }
1834
+ if (factorMatch && method === "DELETE") {
1835
+ const deleted = await this.db.query(`delete from auth.mfa_factors where user_id = $1 and id = $2 returning id`, [factorMatch[1], factorMatch[2]]);
1836
+ if (deleted.rows.length === 0)
1837
+ return authError(404, "mfa_factor_not_found", "MFA factor not found");
1838
+ return json(200, { id: factorMatch[2] });
1839
+ }
1814
1840
  if (path === "admin/users" && method === "GET") {
1815
1841
  const res = await this.db.query(`select * from auth.users order by created_at desc limit 1000`);
1816
- return json(200, { users: res.rows.map((u) => this.userJson(u)), aud: "authenticated" });
1842
+ return json(200, { users: res.rows.map((user) => this.userJson(user)), aud: "authenticated" });
1817
1843
  }
1818
1844
  if (path === "admin/users" && method === "POST") {
1819
1845
  const body = await req.json().catch(() => ({}));
1820
1846
  if (!body.email)
1821
1847
  return authError(400, "validation_failed", "email is required");
1822
1848
  const hashed = body.password ? await hashPassword(body.password) : null;
1823
- const res = await this.db.query(`insert into auth.users
1824
- (aud, role, email, encrypted_password, email_confirmed_at, raw_app_meta_data, raw_user_meta_data)
1825
- values ('authenticated', 'authenticated', $1, $2, case when $3 then now() else null end, $4, $5)
1826
- returning *`, [
1827
- body.email.toLowerCase().trim(),
1828
- hashed,
1829
- body.email_confirm ?? true,
1830
- JSON.stringify({ provider: "email", providers: ["email"], ...body.app_metadata ?? {} }),
1831
- JSON.stringify(body.user_metadata ?? {})
1832
- ]);
1833
- return json(200, this.userJson(res.rows[0]));
1849
+ const created = await this.db.transaction(async (query) => {
1850
+ const res = await query(`insert into auth.users
1851
+ (aud, role, email, encrypted_password, email_confirmed_at, raw_app_meta_data, raw_user_meta_data)
1852
+ values ('authenticated', 'authenticated', $1, $2, case when $3 then now() else null end, $4, $5)
1853
+ returning *`, [
1854
+ body.email.toLowerCase().trim(),
1855
+ hashed,
1856
+ body.email_confirm ?? true,
1857
+ JSON.stringify({ provider: "email", providers: ["email"], ...body.app_metadata ?? {} }),
1858
+ JSON.stringify(body.user_metadata ?? {})
1859
+ ]);
1860
+ const user = res.rows[0];
1861
+ await this.ensureEmailIdentity(user, query);
1862
+ return this.userJsonWithRelations(user, query);
1863
+ });
1864
+ return json(200, created);
1834
1865
  }
1835
1866
  if (idMatch && method === "GET") {
1836
1867
  const res = await this.db.query(`select * from auth.users where id = $1`, [idMatch[1]]);
1837
1868
  if (res.rows.length === 0)
1838
1869
  return authError(404, "user_not_found", "User not found");
1839
- return json(200, this.userJson(res.rows[0]));
1870
+ return json(200, await this.userJsonWithRelations(res.rows[0]));
1840
1871
  }
1841
1872
  if (idMatch && method === "PUT") {
1842
1873
  const body = await req.json().catch(() => ({}));
@@ -1863,10 +1894,18 @@ Or sign in with this link: ${link}`
1863
1894
  if (sets.length === 0)
1864
1895
  return authError(400, "validation_failed", "nothing to update");
1865
1896
  params.push(idMatch[1]);
1866
- const res = await this.db.query(`update auth.users set ${sets.join(", ")}, updated_at = now() where id = $${params.length} returning *`, params);
1867
- if (res.rows.length === 0)
1897
+ const updated = await this.db.transaction(async (query) => {
1898
+ const res = await query(`update auth.users set ${sets.join(", ")}, updated_at = now() where id = $${params.length} returning *`, params);
1899
+ const user = res.rows[0];
1900
+ if (!user)
1901
+ return null;
1902
+ if (typeof body.email === "string")
1903
+ await this.ensureEmailIdentity(user, query);
1904
+ return this.userJsonWithRelations(user, query);
1905
+ });
1906
+ if (!updated)
1868
1907
  return authError(404, "user_not_found", "User not found");
1869
- return json(200, this.userJson(res.rows[0]));
1908
+ return json(200, updated);
1870
1909
  }
1871
1910
  if (idMatch && method === "DELETE") {
1872
1911
  return await this.eraseUser(idMatch[1]);
@@ -2062,17 +2101,26 @@ Or sign in with this link: ${link}`
2062
2101
  if (!await verifyTotp(factor.secret, body.code ?? "")) {
2063
2102
  return authError(422, "mfa_verification_failed", "Invalid TOTP code entered");
2064
2103
  }
2065
- await this.db.query(`update auth.mfa_challenges set verified_at = now() where id = $1`, [challenge.id]);
2066
- if (factor.status !== "verified") {
2067
- await this.db.query(`update auth.mfa_factors set status = 'verified', updated_at = now() where id = $1`, [factorId]);
2068
- }
2069
- const session = await this.sessionFor(user, undefined, {
2070
- aal: "aal2",
2071
- amr: [
2072
- { method: "password", timestamp: Math.floor(Date.now() / 1000) },
2073
- { method: "totp", timestamp: Math.floor(Date.now() / 1000) }
2074
- ]
2104
+ const session = await this.db.transaction(async (query) => {
2105
+ const claimed = await query(`update auth.mfa_challenges set verified_at = now()
2106
+ where id = $1 and factor_id = $2 and verified_at is null and expires_at >= now()
2107
+ returning id`, [challenge.id, factorId]);
2108
+ if (claimed.rows.length === 0)
2109
+ return null;
2110
+ if (factor.status !== "verified") {
2111
+ await query(`update auth.mfa_factors set status = 'verified', updated_at = now() where id = $1`, [factorId]);
2112
+ }
2113
+ return this.sessionFor(user, undefined, {
2114
+ aal: "aal2",
2115
+ amr: [
2116
+ { method: "password", timestamp: Math.floor(Date.now() / 1000) },
2117
+ { method: "totp", timestamp: Math.floor(Date.now() / 1000) }
2118
+ ]
2119
+ }, query);
2075
2120
  });
2121
+ if (!session) {
2122
+ return authError(422, "mfa_verification_failed", "This challenge has already been verified");
2123
+ }
2076
2124
  return json(200, session);
2077
2125
  }
2078
2126
  async unenrollFactor(req, factorId) {
@@ -2110,6 +2158,17 @@ Or sign in with this link: ${link}`
2110
2158
  updated_at: iso(r.updated_at)
2111
2159
  }));
2112
2160
  }
2161
+ async ensureEmailIdentity(user, query = (sql, params) => this.db.query(sql, params)) {
2162
+ if (!user.email)
2163
+ return;
2164
+ await query(`insert into auth.identities (user_id, provider, provider_id, identity_data)
2165
+ values ($1::uuid, 'email', $1::text, $2::jsonb)
2166
+ on conflict (provider, provider_id) do update
2167
+ set identity_data = excluded.identity_data, updated_at = now()`, [user.id, JSON.stringify({ sub: user.id, email: user.email })]);
2168
+ }
2169
+ async userJsonWithRelations(user, query = (sql, params) => this.db.query(sql, params)) {
2170
+ return this.userJson(user, await this.getUserFactors(user.id, query), await this.getUserIdentities(user.id, query));
2171
+ }
2113
2172
  async rotateRefreshToken(token) {
2114
2173
  return this.db.transaction(async (query) => {
2115
2174
  const claimedToken = await this.claimRefreshToken(query, token);
@@ -2183,9 +2242,14 @@ Or sign in with this link: ${link}`
2183
2242
  is_anonymous: u.is_anonymous ?? false
2184
2243
  };
2185
2244
  }
2186
- async sessionTokensFor(userId) {
2245
+ sessionForOAuth(user) {
2246
+ return this.sessionFor(user, undefined, {
2247
+ amr: [{ method: "oauth", timestamp: Math.floor(Date.now() / 1000) }]
2248
+ });
2249
+ }
2250
+ async oauthSessionTokensFor(userId) {
2187
2251
  const res = await this.db.query(`select * from auth.users where id = $1`, [userId]);
2188
- const session = await this.sessionFor(res.rows[0]);
2252
+ const session = await this.sessionForOAuth(res.rows[0]);
2189
2253
  return { access_token: session.access_token, refresh_token: session.refresh_token, expires_in: session.expires_in };
2190
2254
  }
2191
2255
  async sessionFor(user, parentToken, opts, query = (sql, params) => this.db.query(sql, params)) {
@@ -10732,6 +10796,7 @@ function toPgArrayLiteral(array) {
10732
10796
 
10733
10797
  // src/runtime/node/native/engine.ts
10734
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";
10735
10800
  var NATIVE_POSTGRES_MAJOR = DEFAULT_PG_VERSION.split(".")[0];
10736
10801
  function isNativeEngineSupported() {
10737
10802
  return (process.platform === "darwin" || process.platform === "linux") && (process.arch === "arm64" || process.arch === "x64") && (process.platform !== "linux" || isGlibcLinux());
@@ -10763,7 +10828,7 @@ var PINNED_SHA256 = {
10763
10828
  "postgresql-17.7.0-x86_64-apple-darwin": "0dd8c25173524bad4ae8ef6b970da1ac40f4c1f231150c416ccb8cd06feff8f2",
10764
10829
  "postgresql-17.7.0-aarch64-apple-darwin": "727ac08d20a704014a0d51eb3300aa0c8e292c1cf0a1c99d4f4b1002e1420220"
10765
10830
  };
10766
- async function verifyTarball(tarball, key, url) {
10831
+ async function verifyTarball(tarball, key, upstreamUrl, downloadMirror) {
10767
10832
  const actual = createHash2("sha256").update(readFileSync(tarball)).digest("hex");
10768
10833
  const pinned = PINNED_SHA256[key];
10769
10834
  if (pinned) {
@@ -10772,7 +10837,8 @@ async function verifyTarball(tarball, key, url) {
10772
10837
  }
10773
10838
  return;
10774
10839
  }
10775
- const res = await fetchRelease(`${url}.sha256`);
10840
+ const checksumUrl = resolvePostgresDownloadUrl(`${upstreamUrl}.sha256`, downloadMirror);
10841
+ const res = await fetchRelease(checksumUrl);
10776
10842
  if (!res.ok)
10777
10843
  throw new Error(`could not fetch checksum for ${key}: HTTP ${res.status}`);
10778
10844
  const expected = (await res.text()).trim().split(/\s+/)[0].toLowerCase();
@@ -10782,13 +10848,14 @@ async function verifyTarball(tarball, key, url) {
10782
10848
  throw new Error(`postgres binary checksum mismatch for ${key}: expected ${expected}, got ${actual}`);
10783
10849
  }
10784
10850
  }
10785
- async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log) {
10851
+ async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log, downloadMirror) {
10786
10852
  const t = target();
10787
10853
  const root = cacheDir ?? join(homedir(), ".cache", "supacloud-lite");
10788
10854
  const dir = join(root, `postgresql-${version}-${t}`);
10789
10855
  if (isCompleteInstall(dir))
10790
10856
  return dir;
10791
- const url = `https://github.com/theseus-rs/postgresql-binaries/releases/download/${version}/postgresql-${version}-${t}.tar.gz`;
10857
+ const upstreamUrl = `https://github.com/theseus-rs/postgresql-binaries/releases/download/${version}/postgresql-${version}-${t}.tar.gz`;
10858
+ const url = resolvePostgresDownloadUrl(upstreamUrl, downloadMirror);
10792
10859
  mkdirSync(root, { recursive: true });
10793
10860
  const uniq = `${process.pid}-${randomBytes2(6).toString("hex")}`;
10794
10861
  const tarball = join(root, `pg-${version}-${uniq}.tar.gz`);
@@ -10801,7 +10868,7 @@ async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log) {
10801
10868
  if (!res.ok)
10802
10869
  throw new Error(`failed to download ${url}: HTTP ${res.status}`);
10803
10870
  await writeFile(tarball, Buffer.from(await res.arrayBuffer()));
10804
- await verifyTarball(tarball, `postgresql-${version}-${t}`, url);
10871
+ await verifyTarball(tarball, `postgresql-${version}-${t}`, upstreamUrl, downloadMirror);
10805
10872
  mkdirSync(tmpDir, { recursive: true });
10806
10873
  await extractTar({ cwd: tmpDir, file: tarball, gzip: true, preserveOwner: false, strict: true, strip: 1 });
10807
10874
  if (!isCompleteInstall(tmpDir))
@@ -10821,6 +10888,32 @@ async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log) {
10821
10888
  rmSync(tmpDir, { recursive: true, force: true });
10822
10889
  }
10823
10890
  }
10891
+ function resolvePostgresDownloadUrl(upstreamUrl, downloadMirror) {
10892
+ const mirror = postgresDownloadMirror(downloadMirror);
10893
+ return mirror ? `${mirror.toString().replace(/\/+$/, "")}/${upstreamUrl}` : upstreamUrl;
10894
+ }
10895
+ function postgresDownloadMirror(downloadMirror) {
10896
+ const configuredMirror = downloadMirror ?? process.env.SUPACLOUD_LITE_POSTGRES_MIRROR;
10897
+ const mirrorText = configuredMirror?.trim();
10898
+ if (!mirrorText)
10899
+ return;
10900
+ let mirror;
10901
+ try {
10902
+ mirror = new URL(mirrorText);
10903
+ } catch {
10904
+ throw new Error(POSTGRES_MIRROR_URL_ERROR);
10905
+ }
10906
+ const loopbackHttp = mirror.protocol === "http:" && (mirror.hostname === "127.0.0.1" || mirror.hostname === "localhost" || mirror.hostname === "[::1]");
10907
+ if (mirror.protocol !== "https:" && !loopbackHttp)
10908
+ throw new Error(POSTGRES_MIRROR_URL_ERROR);
10909
+ if (mirror.username || mirror.password) {
10910
+ throw new Error("SUPACLOUD_LITE_POSTGRES_MIRROR must not contain credentials");
10911
+ }
10912
+ if (mirrorText.includes("?") || mirrorText.includes("#")) {
10913
+ throw new Error("SUPACLOUD_LITE_POSTGRES_MIRROR must not contain a query or fragment");
10914
+ }
10915
+ return mirror;
10916
+ }
10824
10917
  async function fetchRelease(url) {
10825
10918
  let lastError;
10826
10919
  for (let attempt = 1;attempt <= 3; attempt++) {
@@ -10853,7 +10946,7 @@ async function createNativeEngine(opts) {
10853
10946
  let postgres;
10854
10947
  let removeExitHandler;
10855
10948
  try {
10856
- const installDir = await ensurePostgres(opts.version, opts.cacheDir, opts.log);
10949
+ const installDir = await ensurePostgres(opts.version, opts.cacheDir, opts.log, opts.downloadMirror);
10857
10950
  const bin = (name) => join(installDir, "bin", name);
10858
10951
  if (!existsSync(join(opts.dataDir, "PG_VERSION"))) {
10859
10952
  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.0",
88
+ version: "0.8.2",
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",
@@ -1301,7 +1301,8 @@ class AuthHandler {
1301
1301
  return await this.oauth.authorize(url);
1302
1302
  }
1303
1303
  if (path === "callback" && (method === "GET" || method === "POST")) {
1304
- return await this.oauth.callback(url, (userId) => this.sessionTokensFor(userId));
1304
+ const callbackUrl = method === "POST" ? await this.oauthCallbackUrl(req, url) : url;
1305
+ return await this.oauth.callback(callbackUrl, (userId) => this.oauthSessionTokensFor(userId));
1305
1306
  }
1306
1307
  if (path.startsWith("admin/"))
1307
1308
  return await this.admin(req, ctx, path, method);
@@ -1311,6 +1312,16 @@ class AuthHandler {
1311
1312
  return authError(500, "unexpected_failure", msg);
1312
1313
  }
1313
1314
  }
1315
+ async oauthCallbackUrl(req, url) {
1316
+ const callbackUrl = new URL(url);
1317
+ const form = await req.formData();
1318
+ for (const field of ["code", "state"]) {
1319
+ const value = form.get(field);
1320
+ if (typeof value === "string" && value)
1321
+ callbackUrl.searchParams.set(field, value);
1322
+ }
1323
+ return callbackUrl;
1324
+ }
1314
1325
  async signup(req) {
1315
1326
  const body = await req.json().catch(() => ({}));
1316
1327
  if (!body.email && !body.password) {
@@ -1388,7 +1399,7 @@ class AuthHandler {
1388
1399
  if (!userId)
1389
1400
  return authError(403, "flow_state_not_found", "invalid or expired auth code");
1390
1401
  const ures = await this.db.query(`select * from auth.users where id = $1`, [userId]);
1391
- return json(200, await this.sessionFor(ures.rows[0]));
1402
+ return json(200, await this.sessionForOAuth(ures.rows[0]));
1392
1403
  }
1393
1404
  return authError(400, "invalid_grant", `unsupported grant_type: ${grantType}`);
1394
1405
  }
@@ -1433,17 +1444,18 @@ class AuthHandler {
1433
1444
  sets.push(`is_anonymous = false`);
1434
1445
  sets.push(`raw_app_meta_data = coalesce(raw_app_meta_data, '{}'::jsonb) || '{"provider":"email","providers":["email"]}'::jsonb`);
1435
1446
  }
1436
- if (sets.length === 0)
1437
- return json(200, this.userJson(user));
1438
- params.push(user.id);
1439
- const res = await this.db.query(`update auth.users set ${sets.join(", ")}, updated_at = now() where id = $${params.length} returning *`, params);
1440
- const updated = res.rows[0];
1441
- if (upgradingAnon) {
1442
- await this.db.query(`insert into auth.identities (user_id, provider, provider_id, identity_data)
1443
- values ($1, 'email', $2, $3)
1444
- on conflict (provider, provider_id) do nothing`, [updated.id, updated.id, JSON.stringify({ sub: updated.id, email: updated.email })]);
1447
+ if (sets.length === 0) {
1448
+ return json(200, this.userJson(user, await this.getUserFactors(user.id), await this.getUserIdentities(user.id)));
1445
1449
  }
1446
- return json(200, this.userJson(updated));
1450
+ params.push(user.id);
1451
+ const payload = await this.db.transaction(async (query) => {
1452
+ const res = await query(`update auth.users set ${sets.join(", ")}, updated_at = now() where id = $${params.length} returning *`, params);
1453
+ const updated = res.rows[0];
1454
+ if (body.email)
1455
+ await this.ensureEmailIdentity(updated, query);
1456
+ return this.userJsonWithRelations(updated, query);
1457
+ });
1458
+ return json(200, payload);
1447
1459
  }
1448
1460
  async logout(req, url) {
1449
1461
  const claims = await this.claimsFromBearer(req);
@@ -1473,7 +1485,7 @@ class AuthHandler {
1473
1485
  let user = res.rows[0];
1474
1486
  if (!user) {
1475
1487
  if (!createUser)
1476
- return authError(422, "otp_disabled", "Signups not allowed for otp");
1488
+ return json(200, {});
1477
1489
  if (this.settings.disableSignup)
1478
1490
  return authError(422, "signup_disabled", "Signups not allowed for this instance");
1479
1491
  res = await this.db.query(`insert into auth.users (aud, role, email, raw_app_meta_data, raw_user_meta_data)
@@ -1805,6 +1817,8 @@ Or sign in with this link: ${link}`
1805
1817
  }
1806
1818
  const idMatch = path.match(/^admin\/users\/([0-9a-f-]{36})$/);
1807
1819
  const exportMatch = path.match(/^admin\/users\/([0-9a-f-]{36})\/export$/);
1820
+ const factorsMatch = path.match(/^admin\/users\/([0-9a-f-]{36})\/factors$/);
1821
+ const factorMatch = path.match(/^admin\/users\/([0-9a-f-]{36})\/factors\/([0-9a-f-]{36})$/);
1808
1822
  if (path === "admin/generate_link" && method === "POST") {
1809
1823
  return await this.generateAdminMagicLink(req);
1810
1824
  }
@@ -1816,32 +1830,49 @@ Or sign in with this link: ${link}`
1816
1830
  if (exportMatch && method === "GET") {
1817
1831
  return await this.exportUser(exportMatch[1]);
1818
1832
  }
1833
+ if (factorsMatch && method === "GET") {
1834
+ const user = await this.db.query(`select 1 from auth.users where id = $1`, [factorsMatch[1]]);
1835
+ if (user.rows.length === 0)
1836
+ return authError(404, "user_not_found", "User not found");
1837
+ return json(200, await this.getUserFactors(factorsMatch[1]));
1838
+ }
1839
+ if (factorMatch && method === "DELETE") {
1840
+ const deleted = await this.db.query(`delete from auth.mfa_factors where user_id = $1 and id = $2 returning id`, [factorMatch[1], factorMatch[2]]);
1841
+ if (deleted.rows.length === 0)
1842
+ return authError(404, "mfa_factor_not_found", "MFA factor not found");
1843
+ return json(200, { id: factorMatch[2] });
1844
+ }
1819
1845
  if (path === "admin/users" && method === "GET") {
1820
1846
  const res = await this.db.query(`select * from auth.users order by created_at desc limit 1000`);
1821
- return json(200, { users: res.rows.map((u) => this.userJson(u)), aud: "authenticated" });
1847
+ return json(200, { users: res.rows.map((user) => this.userJson(user)), aud: "authenticated" });
1822
1848
  }
1823
1849
  if (path === "admin/users" && method === "POST") {
1824
1850
  const body = await req.json().catch(() => ({}));
1825
1851
  if (!body.email)
1826
1852
  return authError(400, "validation_failed", "email is required");
1827
1853
  const hashed = body.password ? await hashPassword(body.password) : null;
1828
- const res = await this.db.query(`insert into auth.users
1829
- (aud, role, email, encrypted_password, email_confirmed_at, raw_app_meta_data, raw_user_meta_data)
1830
- values ('authenticated', 'authenticated', $1, $2, case when $3 then now() else null end, $4, $5)
1831
- returning *`, [
1832
- body.email.toLowerCase().trim(),
1833
- hashed,
1834
- body.email_confirm ?? true,
1835
- JSON.stringify({ provider: "email", providers: ["email"], ...body.app_metadata ?? {} }),
1836
- JSON.stringify(body.user_metadata ?? {})
1837
- ]);
1838
- return json(200, this.userJson(res.rows[0]));
1854
+ const created = await this.db.transaction(async (query) => {
1855
+ const res = await query(`insert into auth.users
1856
+ (aud, role, email, encrypted_password, email_confirmed_at, raw_app_meta_data, raw_user_meta_data)
1857
+ values ('authenticated', 'authenticated', $1, $2, case when $3 then now() else null end, $4, $5)
1858
+ returning *`, [
1859
+ body.email.toLowerCase().trim(),
1860
+ hashed,
1861
+ body.email_confirm ?? true,
1862
+ JSON.stringify({ provider: "email", providers: ["email"], ...body.app_metadata ?? {} }),
1863
+ JSON.stringify(body.user_metadata ?? {})
1864
+ ]);
1865
+ const user = res.rows[0];
1866
+ await this.ensureEmailIdentity(user, query);
1867
+ return this.userJsonWithRelations(user, query);
1868
+ });
1869
+ return json(200, created);
1839
1870
  }
1840
1871
  if (idMatch && method === "GET") {
1841
1872
  const res = await this.db.query(`select * from auth.users where id = $1`, [idMatch[1]]);
1842
1873
  if (res.rows.length === 0)
1843
1874
  return authError(404, "user_not_found", "User not found");
1844
- return json(200, this.userJson(res.rows[0]));
1875
+ return json(200, await this.userJsonWithRelations(res.rows[0]));
1845
1876
  }
1846
1877
  if (idMatch && method === "PUT") {
1847
1878
  const body = await req.json().catch(() => ({}));
@@ -1868,10 +1899,18 @@ Or sign in with this link: ${link}`
1868
1899
  if (sets.length === 0)
1869
1900
  return authError(400, "validation_failed", "nothing to update");
1870
1901
  params.push(idMatch[1]);
1871
- const res = await this.db.query(`update auth.users set ${sets.join(", ")}, updated_at = now() where id = $${params.length} returning *`, params);
1872
- if (res.rows.length === 0)
1902
+ const updated = await this.db.transaction(async (query) => {
1903
+ const res = await query(`update auth.users set ${sets.join(", ")}, updated_at = now() where id = $${params.length} returning *`, params);
1904
+ const user = res.rows[0];
1905
+ if (!user)
1906
+ return null;
1907
+ if (typeof body.email === "string")
1908
+ await this.ensureEmailIdentity(user, query);
1909
+ return this.userJsonWithRelations(user, query);
1910
+ });
1911
+ if (!updated)
1873
1912
  return authError(404, "user_not_found", "User not found");
1874
- return json(200, this.userJson(res.rows[0]));
1913
+ return json(200, updated);
1875
1914
  }
1876
1915
  if (idMatch && method === "DELETE") {
1877
1916
  return await this.eraseUser(idMatch[1]);
@@ -2067,17 +2106,26 @@ Or sign in with this link: ${link}`
2067
2106
  if (!await verifyTotp(factor.secret, body.code ?? "")) {
2068
2107
  return authError(422, "mfa_verification_failed", "Invalid TOTP code entered");
2069
2108
  }
2070
- await this.db.query(`update auth.mfa_challenges set verified_at = now() where id = $1`, [challenge.id]);
2071
- if (factor.status !== "verified") {
2072
- await this.db.query(`update auth.mfa_factors set status = 'verified', updated_at = now() where id = $1`, [factorId]);
2073
- }
2074
- const session = await this.sessionFor(user, undefined, {
2075
- aal: "aal2",
2076
- amr: [
2077
- { method: "password", timestamp: Math.floor(Date.now() / 1000) },
2078
- { method: "totp", timestamp: Math.floor(Date.now() / 1000) }
2079
- ]
2109
+ const session = await this.db.transaction(async (query) => {
2110
+ const claimed = await query(`update auth.mfa_challenges set verified_at = now()
2111
+ where id = $1 and factor_id = $2 and verified_at is null and expires_at >= now()
2112
+ returning id`, [challenge.id, factorId]);
2113
+ if (claimed.rows.length === 0)
2114
+ return null;
2115
+ if (factor.status !== "verified") {
2116
+ await query(`update auth.mfa_factors set status = 'verified', updated_at = now() where id = $1`, [factorId]);
2117
+ }
2118
+ return this.sessionFor(user, undefined, {
2119
+ aal: "aal2",
2120
+ amr: [
2121
+ { method: "password", timestamp: Math.floor(Date.now() / 1000) },
2122
+ { method: "totp", timestamp: Math.floor(Date.now() / 1000) }
2123
+ ]
2124
+ }, query);
2080
2125
  });
2126
+ if (!session) {
2127
+ return authError(422, "mfa_verification_failed", "This challenge has already been verified");
2128
+ }
2081
2129
  return json(200, session);
2082
2130
  }
2083
2131
  async unenrollFactor(req, factorId) {
@@ -2115,6 +2163,17 @@ Or sign in with this link: ${link}`
2115
2163
  updated_at: iso(r.updated_at)
2116
2164
  }));
2117
2165
  }
2166
+ async ensureEmailIdentity(user, query = (sql, params) => this.db.query(sql, params)) {
2167
+ if (!user.email)
2168
+ return;
2169
+ await query(`insert into auth.identities (user_id, provider, provider_id, identity_data)
2170
+ values ($1::uuid, 'email', $1::text, $2::jsonb)
2171
+ on conflict (provider, provider_id) do update
2172
+ set identity_data = excluded.identity_data, updated_at = now()`, [user.id, JSON.stringify({ sub: user.id, email: user.email })]);
2173
+ }
2174
+ async userJsonWithRelations(user, query = (sql, params) => this.db.query(sql, params)) {
2175
+ return this.userJson(user, await this.getUserFactors(user.id, query), await this.getUserIdentities(user.id, query));
2176
+ }
2118
2177
  async rotateRefreshToken(token) {
2119
2178
  return this.db.transaction(async (query) => {
2120
2179
  const claimedToken = await this.claimRefreshToken(query, token);
@@ -2188,9 +2247,14 @@ Or sign in with this link: ${link}`
2188
2247
  is_anonymous: u.is_anonymous ?? false
2189
2248
  };
2190
2249
  }
2191
- async sessionTokensFor(userId) {
2250
+ sessionForOAuth(user) {
2251
+ return this.sessionFor(user, undefined, {
2252
+ amr: [{ method: "oauth", timestamp: Math.floor(Date.now() / 1000) }]
2253
+ });
2254
+ }
2255
+ async oauthSessionTokensFor(userId) {
2192
2256
  const res = await this.db.query(`select * from auth.users where id = $1`, [userId]);
2193
- const session = await this.sessionFor(res.rows[0]);
2257
+ const session = await this.sessionForOAuth(res.rows[0]);
2194
2258
  return { access_token: session.access_token, refresh_token: session.refresh_token, expires_in: session.expires_in };
2195
2259
  }
2196
2260
  async sessionFor(user, parentToken, opts, query = (sql, params) => this.db.query(sql, params)) {
@@ -10745,6 +10809,7 @@ function toPgArrayLiteral(array) {
10745
10809
 
10746
10810
  // src/runtime/node/native/engine.ts
10747
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";
10748
10813
  var NATIVE_POSTGRES_MAJOR = DEFAULT_PG_VERSION.split(".")[0];
10749
10814
  function isNativeEngineSupported() {
10750
10815
  return (process.platform === "darwin" || process.platform === "linux") && (process.arch === "arm64" || process.arch === "x64") && (process.platform !== "linux" || isGlibcLinux());
@@ -10776,7 +10841,7 @@ var PINNED_SHA256 = {
10776
10841
  "postgresql-17.7.0-x86_64-apple-darwin": "0dd8c25173524bad4ae8ef6b970da1ac40f4c1f231150c416ccb8cd06feff8f2",
10777
10842
  "postgresql-17.7.0-aarch64-apple-darwin": "727ac08d20a704014a0d51eb3300aa0c8e292c1cf0a1c99d4f4b1002e1420220"
10778
10843
  };
10779
- async function verifyTarball(tarball, key, url) {
10844
+ async function verifyTarball(tarball, key, upstreamUrl, downloadMirror) {
10780
10845
  const actual = createHash2("sha256").update(readFileSync(tarball)).digest("hex");
10781
10846
  const pinned = PINNED_SHA256[key];
10782
10847
  if (pinned) {
@@ -10785,7 +10850,8 @@ async function verifyTarball(tarball, key, url) {
10785
10850
  }
10786
10851
  return;
10787
10852
  }
10788
- const res = await fetchRelease(`${url}.sha256`);
10853
+ const checksumUrl = resolvePostgresDownloadUrl(`${upstreamUrl}.sha256`, downloadMirror);
10854
+ const res = await fetchRelease(checksumUrl);
10789
10855
  if (!res.ok)
10790
10856
  throw new Error(`could not fetch checksum for ${key}: HTTP ${res.status}`);
10791
10857
  const expected = (await res.text()).trim().split(/\s+/)[0].toLowerCase();
@@ -10795,13 +10861,14 @@ async function verifyTarball(tarball, key, url) {
10795
10861
  throw new Error(`postgres binary checksum mismatch for ${key}: expected ${expected}, got ${actual}`);
10796
10862
  }
10797
10863
  }
10798
- async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log) {
10864
+ async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log, downloadMirror) {
10799
10865
  const t = target();
10800
10866
  const root = cacheDir ?? join2(homedir(), ".cache", "supacloud-lite");
10801
10867
  const dir = join2(root, `postgresql-${version}-${t}`);
10802
10868
  if (isCompleteInstall(dir))
10803
10869
  return dir;
10804
- const url = `https://github.com/theseus-rs/postgresql-binaries/releases/download/${version}/postgresql-${version}-${t}.tar.gz`;
10870
+ const upstreamUrl = `https://github.com/theseus-rs/postgresql-binaries/releases/download/${version}/postgresql-${version}-${t}.tar.gz`;
10871
+ const url = resolvePostgresDownloadUrl(upstreamUrl, downloadMirror);
10805
10872
  mkdirSync(root, { recursive: true });
10806
10873
  const uniq = `${process.pid}-${randomBytes2(6).toString("hex")}`;
10807
10874
  const tarball = join2(root, `pg-${version}-${uniq}.tar.gz`);
@@ -10814,7 +10881,7 @@ async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log) {
10814
10881
  if (!res.ok)
10815
10882
  throw new Error(`failed to download ${url}: HTTP ${res.status}`);
10816
10883
  await writeFile2(tarball, Buffer.from(await res.arrayBuffer()));
10817
- await verifyTarball(tarball, `postgresql-${version}-${t}`, url);
10884
+ await verifyTarball(tarball, `postgresql-${version}-${t}`, upstreamUrl, downloadMirror);
10818
10885
  mkdirSync(tmpDir, { recursive: true });
10819
10886
  await extractTar({ cwd: tmpDir, file: tarball, gzip: true, preserveOwner: false, strict: true, strip: 1 });
10820
10887
  if (!isCompleteInstall(tmpDir))
@@ -10834,6 +10901,32 @@ async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log) {
10834
10901
  rmSync(tmpDir, { recursive: true, force: true });
10835
10902
  }
10836
10903
  }
10904
+ function resolvePostgresDownloadUrl(upstreamUrl, downloadMirror) {
10905
+ const mirror = postgresDownloadMirror(downloadMirror);
10906
+ return mirror ? `${mirror.toString().replace(/\/+$/, "")}/${upstreamUrl}` : upstreamUrl;
10907
+ }
10908
+ function postgresDownloadMirror(downloadMirror) {
10909
+ const configuredMirror = downloadMirror ?? process.env.SUPACLOUD_LITE_POSTGRES_MIRROR;
10910
+ const mirrorText = configuredMirror?.trim();
10911
+ if (!mirrorText)
10912
+ return;
10913
+ let mirror;
10914
+ try {
10915
+ mirror = new URL(mirrorText);
10916
+ } catch {
10917
+ throw new Error(POSTGRES_MIRROR_URL_ERROR);
10918
+ }
10919
+ const loopbackHttp = mirror.protocol === "http:" && (mirror.hostname === "127.0.0.1" || mirror.hostname === "localhost" || mirror.hostname === "[::1]");
10920
+ if (mirror.protocol !== "https:" && !loopbackHttp)
10921
+ throw new Error(POSTGRES_MIRROR_URL_ERROR);
10922
+ if (mirror.username || mirror.password) {
10923
+ throw new Error("SUPACLOUD_LITE_POSTGRES_MIRROR must not contain credentials");
10924
+ }
10925
+ if (mirrorText.includes("?") || mirrorText.includes("#")) {
10926
+ throw new Error("SUPACLOUD_LITE_POSTGRES_MIRROR must not contain a query or fragment");
10927
+ }
10928
+ return mirror;
10929
+ }
10837
10930
  async function fetchRelease(url) {
10838
10931
  let lastError;
10839
10932
  for (let attempt = 1;attempt <= 3; attempt++) {
@@ -10866,7 +10959,7 @@ async function createNativeEngine(opts) {
10866
10959
  let postgres;
10867
10960
  let removeExitHandler;
10868
10961
  try {
10869
- const installDir = await ensurePostgres(opts.version, opts.cacheDir, opts.log);
10962
+ const installDir = await ensurePostgres(opts.version, opts.cacheDir, opts.log, opts.downloadMirror);
10870
10963
  const bin = (name) => join2(installDir, "bin", name);
10871
10964
  if (!existsSync(join2(opts.dataDir, "PG_VERSION"))) {
10872
10965
  mkdirSync(opts.dataDir, { recursive: true });
@@ -97,6 +97,7 @@ export declare class AuthHandler {
97
97
  private phoneDeliveriesSettledBeforeDeadline;
98
98
  /** Dispatch one `/auth/v1/*` request. Any thrown error becomes a 500 `unexpected_failure`. */
99
99
  handle(req: Request, ctx: RequestContext, url: URL): Promise<Response>;
100
+ private oauthCallbackUrl;
100
101
  private signup;
101
102
  private token;
102
103
  private getUser;
@@ -159,6 +160,8 @@ export declare class AuthHandler {
159
160
  private getUserFactors;
160
161
  /** GoTrue-shaped identities for a user (linked providers), from auth.identities. */
161
162
  private getUserIdentities;
163
+ private ensureEmailIdentity;
164
+ private userJsonWithRelations;
162
165
  private rotateRefreshToken;
163
166
  private claimRefreshToken;
164
167
  private refreshSessionStartedAt;
@@ -167,8 +170,8 @@ export declare class AuthHandler {
167
170
  private claimsFromBearer;
168
171
  /** Shape a user row into the GoTrue user object supabase-js expects. */
169
172
  userJson(u: UserRow, factors?: Record<string, unknown>[], identities?: Record<string, unknown>[]): Record<string, unknown>;
170
- /** Session tokens for a bare user id (used by the OAuth implicit callback). */
171
- private sessionTokensFor;
173
+ private sessionForOAuth;
174
+ private oauthSessionTokensFor;
172
175
  private sessionFor;
173
176
  }
174
177
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/runtime/auth/handler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/runtime/auth/handler.ts"],"names":[],"mappings":"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"}
@@ -10,10 +10,17 @@ export interface NativeEngineOptions {
10
10
  cacheDir?: string;
11
11
  /** sink for progress lines (download, install); no-op when omitted */
12
12
  log?: (msg: string) => void;
13
+ /** Optional HTTPS or loopback HTTP prefix for proxying the PostgreSQL release download. */
14
+ downloadMirror?: string;
13
15
  }
14
16
  export declare function isNativeEngineSupported(): boolean;
15
17
  /** 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>;
18
+ export declare function ensurePostgres(version?: string, cacheDir?: string, log?: (m: string) => void, downloadMirror?: string): Promise<string>;
19
+ /**
20
+ * Prefix a trusted GitHub release URL with an operator-selected mirror. The
21
+ * archive is still checked against the pinned or published SHA-256 before use.
22
+ */
23
+ export declare function resolvePostgresDownloadUrl(upstreamUrl: string, downloadMirror?: string): string;
17
24
  /**
18
25
  * Boot the embedded Postgres child (initdb on first run) and return a {@link DbEngine}
19
26
  * 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;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;IAC3B,2FAA2F;IAC3F,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,wBAAgB,uBAAuB,IAAI,OAAO,CAIjD;AA6DD,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.0",
3
+ "version": "0.8.2",
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",