@supacloud/lite 0.29.0 → 0.29.1

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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.29.1](https://github.com/vibeunion/supacloud/compare/supacloud-lite-v0.29.0...supacloud-lite-v0.29.1) (2026-10-15)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **lite:** enforce read-only table and view requests ([#1941](https://github.com/vibeunion/supacloud/issues/1941)) ([d726716](https://github.com/vibeunion/supacloud/commit/d726716a1492a5a581f99a1aa40f15a1125a23aa))
9
+
3
10
  ## [0.29.0](https://github.com/vibeunion/supacloud/compare/supacloud-lite-v0.28.3...supacloud-lite-v0.29.0) (2026-10-15)
4
11
 
5
12
 
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import { dirname as dirname8, join as join10, resolve as resolve6 } from "path";
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "@supacloud/lite",
11
- version: "0.29.0",
11
+ version: "0.29.1",
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",
@@ -8365,10 +8365,10 @@ function renderFilter(f, alias) {
8365
8365
  }
8366
8366
 
8367
8367
  // src/runtime/rest/errors.ts
8368
- function statusForSqlState(code) {
8368
+ function statusForSqlState(code, authenticated = true) {
8369
8369
  switch (code) {
8370
8370
  case "42501":
8371
- return 403;
8371
+ return authenticated ? 403 : 401;
8372
8372
  case "25006":
8373
8373
  return 405;
8374
8374
  case "23503":
@@ -8386,7 +8386,7 @@ function statusForSqlState(code) {
8386
8386
  return 400;
8387
8387
  }
8388
8388
  }
8389
- function errorToResponse(e) {
8389
+ function errorToResponse(e, authenticated = true) {
8390
8390
  if (e instanceof ApiError) {
8391
8391
  return jsonResponse(e.status, e.body);
8392
8392
  }
@@ -8395,7 +8395,7 @@ function errorToResponse(e) {
8395
8395
  }
8396
8396
  const pg = e;
8397
8397
  if (pg && typeof pg.code === "string" && /^[0-9A-Z]{5}$/.test(pg.code)) {
8398
- return jsonResponse(statusForSqlState(pg.code), {
8398
+ return jsonResponse(statusForSqlState(pg.code, authenticated), {
8399
8399
  code: pg.code,
8400
8400
  message: pg.message,
8401
8401
  details: pg.detail ?? null,
@@ -8515,7 +8515,7 @@ class RestHandler {
8515
8515
  }
8516
8516
  return await this.handleTable(req, ctx, url, schema, decodeURIComponent(rest));
8517
8517
  } catch (e) {
8518
- return errorToResponse(e);
8518
+ return errorToResponse(e, ctx.role !== "anon");
8519
8519
  }
8520
8520
  }
8521
8521
  async handleTable(req, ctx, url, schema, table) {
@@ -8543,7 +8543,10 @@ class RestHandler {
8543
8543
  ...explain.options.map((o) => o.toUpperCase()),
8544
8544
  `FORMAT ${explain.format}`
8545
8545
  ].join(", ");
8546
- const res = await this.db.withContext(ctx, (query) => query(`explain (${explainOpts}) ${plan.sql}`, plan.params));
8546
+ const res = await this.db.withContext(ctx, async (query) => {
8547
+ await query("set transaction read only");
8548
+ return query(`explain (${explainOpts}) ${plan.sql}`, plan.params);
8549
+ });
8547
8550
  const cell = res.rows[0]["QUERY PLAN"];
8548
8551
  if (explain.format === "JSON") {
8549
8552
  const body = typeof cell === "string" ? cell : JSON.stringify(cell);
@@ -8561,6 +8564,7 @@ class RestHandler {
8561
8564
  }
8562
8565
  const built = builder.buildSelect(table, { count: prefer.count !== undefined });
8563
8566
  const { rows, count } = await this.db.withContext(ctx, async (query) => {
8567
+ await query("set transaction read only");
8564
8568
  const res = await query(built.sql, built.params);
8565
8569
  let count = null;
8566
8570
  if (built.countSql) {
@@ -8716,6 +8720,8 @@ class RestHandler {
8716
8720
  const fn = pickOverload(fns, Object.keys(args));
8717
8721
  const call = renderCall(schema, fn, args);
8718
8722
  const q = parseQuery(searchParams);
8723
+ if (method === "GET" || method === "HEAD")
8724
+ applyRequestRange(q, req);
8719
8725
  if (fn.volatility === "v") {
8720
8726
  return await this.runRpc(ctx, fn, call, q, prefer, wantsObject, wantsCsv, schema, method).finally(() => this.db.invalidateSchemaCache());
8721
8727
  }
@@ -8740,6 +8746,10 @@ class RestHandler {
8740
8746
  const res = await withRpcContext((query) => query(`select coalesce(json_agg(_v), '[]'::json) as body from ${call} as _t(_v)`, []));
8741
8747
  return jsonResponse(200, res.rows[0].body);
8742
8748
  }
8749
+ if (this.maxRows !== undefined) {
8750
+ const requested = q.limits.get("");
8751
+ q.limits.set("", requested === undefined ? this.maxRows : Math.min(requested, this.maxRows));
8752
+ }
8743
8753
  const alias = "_f";
8744
8754
  const exprs = [];
8745
8755
  for (const item of q.select) {
@@ -8795,6 +8805,7 @@ function assertObjectCardinality(rows) {
8795
8805
  }
8796
8806
  function pickOverload(fns, providedNames) {
8797
8807
  const provided = new Set(providedNames);
8808
+ const exact = [];
8798
8809
  let best = null;
8799
8810
  for (const fn of fns) {
8800
8811
  const names = new Set(fn.args.map((a) => a.name));
@@ -8802,11 +8813,20 @@ function pickOverload(fns, providedNames) {
8802
8813
  if (!allProvidedKnown)
8803
8814
  continue;
8804
8815
  if (fn.args.length === provided.size)
8805
- return fn;
8816
+ exact.push(fn);
8806
8817
  if (!best)
8807
8818
  best = fn;
8808
8819
  }
8809
- return best ?? fns[0];
8820
+ if (exact.length > 1) {
8821
+ const candidates = exact.map((fn) => `${fn.schema}.${fn.name}(${fn.args.map((arg) => `${arg.name} => ${arg.type}`).join(", ")})`);
8822
+ throw new ApiError(300, {
8823
+ code: "PGRST203",
8824
+ message: `Could not choose the best candidate function between: ${candidates.join(", ")}`,
8825
+ details: null,
8826
+ hint: "Try renaming the parameters or the function itself in the database so function overloading can be resolved"
8827
+ });
8828
+ }
8829
+ return exact[0] ?? best ?? fns[0];
8810
8830
  }
8811
8831
  function renderCall(schema, fn, args) {
8812
8832
  const parts = [];
package/dist/index.js CHANGED
@@ -88,7 +88,7 @@ function randomToken(bytes = 32) {
88
88
  // package.json
89
89
  var package_default = {
90
90
  name: "@supacloud/lite",
91
- version: "0.29.0",
91
+ version: "0.29.1",
92
92
  description: "Bun-native, single-project Supabase-compatible backend powered by PGlite or native PostgreSQL",
93
93
  type: "module",
94
94
  license: "Apache-2.0",
@@ -8359,10 +8359,10 @@ function renderFilter(f, alias) {
8359
8359
  }
8360
8360
 
8361
8361
  // src/runtime/rest/errors.ts
8362
- function statusForSqlState(code) {
8362
+ function statusForSqlState(code, authenticated = true) {
8363
8363
  switch (code) {
8364
8364
  case "42501":
8365
- return 403;
8365
+ return authenticated ? 403 : 401;
8366
8366
  case "25006":
8367
8367
  return 405;
8368
8368
  case "23503":
@@ -8380,7 +8380,7 @@ function statusForSqlState(code) {
8380
8380
  return 400;
8381
8381
  }
8382
8382
  }
8383
- function errorToResponse(e) {
8383
+ function errorToResponse(e, authenticated = true) {
8384
8384
  if (e instanceof ApiError) {
8385
8385
  return jsonResponse(e.status, e.body);
8386
8386
  }
@@ -8389,7 +8389,7 @@ function errorToResponse(e) {
8389
8389
  }
8390
8390
  const pg = e;
8391
8391
  if (pg && typeof pg.code === "string" && /^[0-9A-Z]{5}$/.test(pg.code)) {
8392
- return jsonResponse(statusForSqlState(pg.code), {
8392
+ return jsonResponse(statusForSqlState(pg.code, authenticated), {
8393
8393
  code: pg.code,
8394
8394
  message: pg.message,
8395
8395
  details: pg.detail ?? null,
@@ -8509,7 +8509,7 @@ class RestHandler {
8509
8509
  }
8510
8510
  return await this.handleTable(req, ctx, url, schema, decodeURIComponent(rest));
8511
8511
  } catch (e) {
8512
- return errorToResponse(e);
8512
+ return errorToResponse(e, ctx.role !== "anon");
8513
8513
  }
8514
8514
  }
8515
8515
  async handleTable(req, ctx, url, schema, table) {
@@ -8537,7 +8537,10 @@ class RestHandler {
8537
8537
  ...explain.options.map((o) => o.toUpperCase()),
8538
8538
  `FORMAT ${explain.format}`
8539
8539
  ].join(", ");
8540
- const res = await this.db.withContext(ctx, (query) => query(`explain (${explainOpts}) ${plan.sql}`, plan.params));
8540
+ const res = await this.db.withContext(ctx, async (query) => {
8541
+ await query("set transaction read only");
8542
+ return query(`explain (${explainOpts}) ${plan.sql}`, plan.params);
8543
+ });
8541
8544
  const cell = res.rows[0]["QUERY PLAN"];
8542
8545
  if (explain.format === "JSON") {
8543
8546
  const body = typeof cell === "string" ? cell : JSON.stringify(cell);
@@ -8555,6 +8558,7 @@ class RestHandler {
8555
8558
  }
8556
8559
  const built = builder.buildSelect(table, { count: prefer.count !== undefined });
8557
8560
  const { rows, count } = await this.db.withContext(ctx, async (query) => {
8561
+ await query("set transaction read only");
8558
8562
  const res = await query(built.sql, built.params);
8559
8563
  let count = null;
8560
8564
  if (built.countSql) {
@@ -8710,6 +8714,8 @@ class RestHandler {
8710
8714
  const fn = pickOverload(fns, Object.keys(args));
8711
8715
  const call = renderCall(schema, fn, args);
8712
8716
  const q = parseQuery(searchParams);
8717
+ if (method === "GET" || method === "HEAD")
8718
+ applyRequestRange(q, req);
8713
8719
  if (fn.volatility === "v") {
8714
8720
  return await this.runRpc(ctx, fn, call, q, prefer, wantsObject, wantsCsv, schema, method).finally(() => this.db.invalidateSchemaCache());
8715
8721
  }
@@ -8734,6 +8740,10 @@ class RestHandler {
8734
8740
  const res = await withRpcContext((query) => query(`select coalesce(json_agg(_v), '[]'::json) as body from ${call} as _t(_v)`, []));
8735
8741
  return jsonResponse(200, res.rows[0].body);
8736
8742
  }
8743
+ if (this.maxRows !== undefined) {
8744
+ const requested = q.limits.get("");
8745
+ q.limits.set("", requested === undefined ? this.maxRows : Math.min(requested, this.maxRows));
8746
+ }
8737
8747
  const alias = "_f";
8738
8748
  const exprs = [];
8739
8749
  for (const item of q.select) {
@@ -8789,6 +8799,7 @@ function assertObjectCardinality(rows) {
8789
8799
  }
8790
8800
  function pickOverload(fns, providedNames) {
8791
8801
  const provided = new Set(providedNames);
8802
+ const exact = [];
8792
8803
  let best = null;
8793
8804
  for (const fn of fns) {
8794
8805
  const names = new Set(fn.args.map((a) => a.name));
@@ -8796,11 +8807,20 @@ function pickOverload(fns, providedNames) {
8796
8807
  if (!allProvidedKnown)
8797
8808
  continue;
8798
8809
  if (fn.args.length === provided.size)
8799
- return fn;
8810
+ exact.push(fn);
8800
8811
  if (!best)
8801
8812
  best = fn;
8802
8813
  }
8803
- return best ?? fns[0];
8814
+ if (exact.length > 1) {
8815
+ const candidates = exact.map((fn) => `${fn.schema}.${fn.name}(${fn.args.map((arg) => `${arg.name} => ${arg.type}`).join(", ")})`);
8816
+ throw new ApiError(300, {
8817
+ code: "PGRST203",
8818
+ message: `Could not choose the best candidate function between: ${candidates.join(", ")}`,
8819
+ details: null,
8820
+ hint: "Try renaming the parameters or the function itself in the database so function overloading can be resolved"
8821
+ });
8822
+ }
8823
+ return exact[0] ?? best ?? fns[0];
8804
8824
  }
8805
8825
  function renderCall(schema, fn, args) {
8806
8826
  const parts = [];
@@ -1,11 +1,11 @@
1
1
  /** HTTP status for a Postgres SQLSTATE, following PostgREST's mapping. */
2
- export declare function statusForSqlState(code: string): number;
2
+ export declare function statusForSqlState(code: string, authenticated?: boolean): number;
3
3
  /**
4
4
  * Normalize any thrown value into a JSON Response: ApiError/ParseError keep
5
5
  * their status; anything with a 5-char SQLSTATE is mapped via
6
6
  * {@link statusForSqlState}; everything else becomes a 500.
7
7
  */
8
- export declare function errorToResponse(e: unknown): Response;
8
+ export declare function errorToResponse(e: unknown, authenticated?: boolean): Response;
9
9
  /** JSON Response helper; status 204 sends no body. */
10
10
  export declare function jsonResponse(status: number, body: unknown, headers?: Record<string, string>): Response;
11
11
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/runtime/rest/errors.ts"],"names":[],"mappings":"AAQA,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoBtD;AAUD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,GAAG,QAAQ,CAkBpD;AAED,sDAAsD;AACtD,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACnC,QAAQ,CAKV"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/runtime/rest/errors.ts"],"names":[],"mappings":"AAQA,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,UAAO,GAAG,MAAM,CAoB5E;AAUD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,aAAa,UAAO,GAAG,QAAQ,CAkB1E;AAED,sDAAsD;AACtD,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACnC,QAAQ,CAKV"}
@@ -1 +1 @@
1
- {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/runtime/rest/handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAA4B,KAAK,QAAQ,EAAmC,MAAM,mBAAmB,CAAA;AAC5G,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,aAAa,CAAA;AAyF3D,oFAAoF;AACpF,qBAAa,WAAW;IAOpB,OAAO,CAAC,EAAE;IANZ,8FAA8F;IAC9F,OAAO,CAAC,cAAc,CAAU;IAChC,8EAA8E;IAC9E,OAAO,CAAC,OAAO,CAAC,CAAQ;gBAGd,EAAE,EAAE,QAAQ,EACpB,IAAI,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE;IAMxD,4FAA4F;IACtF,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC;YAmC9D,WAAW;YAsHX,WAAW;IAgDzB,OAAO,CAAC,YAAY;YAqCN,SAAS;YAqET,MAAM;CA4FrB"}
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/runtime/rest/handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAA4B,KAAK,QAAQ,EAAmC,MAAM,mBAAmB,CAAA;AAC5G,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,aAAa,CAAA;AAyF3D,oFAAoF;AACpF,qBAAa,WAAW;IAOpB,OAAO,CAAC,EAAE;IANZ,8FAA8F;IAC9F,OAAO,CAAC,cAAc,CAAU;IAChC,8EAA8E;IAC9E,OAAO,CAAC,OAAO,CAAC,CAAQ;gBAGd,EAAE,EAAE,QAAQ,EACpB,IAAI,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE;IAMxD,4FAA4F;IACtF,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC;YAmC9D,WAAW;YAyHX,WAAW;IAgDzB,OAAO,CAAC,YAAY;YAqCN,SAAS;YAsET,MAAM;CAgGrB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/lite",
3
- "version": "0.29.0",
3
+ "version": "0.29.1",
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",