@supacloud/lite 0.28.3 → 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 +14 -0
- package/dist/cli.js +29 -9
- package/dist/index.js +29 -9
- package/dist/runtime/rest/errors.d.ts +2 -2
- package/dist/runtime/rest/errors.d.ts.map +1 -1
- package/dist/runtime/rest/handler.d.ts.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
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
|
+
|
|
10
|
+
## [0.29.0](https://github.com/vibeunion/supacloud/compare/supacloud-lite-v0.28.3...supacloud-lite-v0.29.0) (2026-10-15)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* reduce approval infrastructure wiring and reuse business contracts ([#1948](https://github.com/vibeunion/supacloud/issues/1948)) ([7c68543](https://github.com/vibeunion/supacloud/commit/7c6854338968209c2bbd658782224db238650aeb))
|
|
16
|
+
|
|
3
17
|
## [0.28.3](https://github.com/vibeunion/supacloud/compare/supacloud-lite-v0.28.2...supacloud-lite-v0.28.3) (2026-10-15)
|
|
4
18
|
|
|
5
19
|
|
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.
|
|
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) =>
|
|
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
|
-
|
|
8816
|
+
exact.push(fn);
|
|
8806
8817
|
if (!best)
|
|
8807
8818
|
best = fn;
|
|
8808
8819
|
}
|
|
8809
|
-
|
|
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.
|
|
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) =>
|
|
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
|
-
|
|
8810
|
+
exact.push(fn);
|
|
8800
8811
|
if (!best)
|
|
8801
8812
|
best = fn;
|
|
8802
8813
|
}
|
|
8803
|
-
|
|
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,
|
|
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;
|
|
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"}
|