@supacloud/lite 0.23.0 → 0.25.0
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 +183 -107
- package/dist/index.js +172 -101
- package/dist/runtime/auth/handler.d.ts.map +1 -1
- package/dist/runtime/auth/oauth.d.ts +3 -2
- package/dist/runtime/auth/oauth.d.ts.map +1 -1
- package/dist/runtime/db/database.d.ts +2 -2
- package/dist/runtime/db/database.d.ts.map +1 -1
- package/dist/runtime/db/engine.d.ts +3 -3
- package/dist/runtime/db/engine.d.ts.map +1 -1
- package/dist/runtime/db/schema-diff.d.ts.map +1 -1
- package/dist/runtime/functions/handler.d.ts.map +1 -1
- package/dist/runtime/gen-types.d.ts.map +1 -1
- package/dist/runtime/graphql.d.ts +1 -1
- package/dist/runtime/graphql.d.ts.map +1 -1
- package/dist/runtime/jwt.d.ts.map +1 -1
- package/dist/runtime/node/bundle-function.d.ts.map +1 -1
- package/dist/runtime/node/native/engine.d.ts.map +1 -1
- package/dist/runtime/node/native/wire.d.ts +2 -2
- package/dist/runtime/node/native/wire.d.ts.map +1 -1
- package/dist/runtime/realtime/engine.d.ts.map +1 -1
- package/dist/runtime/rest/build.d.ts.map +1 -1
- package/dist/runtime/rest/handler.d.ts.map +1 -1
- package/dist/runtime/storage/handler.d.ts.map +1 -1
- package/dist/runtime/storage/image-transform-cache.d.ts.map +1 -1
- package/dist/runtime/storage/image-transform.d.ts.map +1 -1
- package/package.json +2 -2
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.25.0",
|
|
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",
|
|
@@ -74,7 +74,7 @@ var package_default = {
|
|
|
74
74
|
"@supacloud/elysia": "file:../elysia",
|
|
75
75
|
"@supabase/supabase-js": "^2.117.3",
|
|
76
76
|
"@types/bun": "^1.4.2",
|
|
77
|
-
effect: "
|
|
77
|
+
effect: "4.0.2",
|
|
78
78
|
elysia: "2.0.0-beta.21",
|
|
79
79
|
jose: "^6.2.11",
|
|
80
80
|
typescript: "^7.0.2"
|
|
@@ -160,6 +160,8 @@ async function verifyJwt(token, secret) {
|
|
|
160
160
|
if (parts.length !== 3)
|
|
161
161
|
return null;
|
|
162
162
|
const [header, payload, signature] = parts;
|
|
163
|
+
if (!header || !payload || !signature)
|
|
164
|
+
return null;
|
|
163
165
|
try {
|
|
164
166
|
const head = JSON.parse(decoder.decode(base64UrlToBytes(header)));
|
|
165
167
|
if (head.alg !== "HS256")
|
|
@@ -189,7 +191,7 @@ var SUPACLOUD_LITE_VERSION = package_default.version;
|
|
|
189
191
|
function globToRegExp(pattern) {
|
|
190
192
|
let out = "";
|
|
191
193
|
for (let i = 0;i < pattern.length; i++) {
|
|
192
|
-
const c = pattern
|
|
194
|
+
const c = pattern.charAt(i);
|
|
193
195
|
if (c === "*") {
|
|
194
196
|
if (pattern[i + 1] === "*") {
|
|
195
197
|
out += ".*";
|
|
@@ -242,11 +244,11 @@ var PRESETS = {
|
|
|
242
244
|
userInfoUrl: "https://openidconnect.googleapis.com/v1/userinfo",
|
|
243
245
|
scopes: "openid email profile",
|
|
244
246
|
profileMap: (r) => ({
|
|
245
|
-
id: r
|
|
246
|
-
email: r
|
|
247
|
-
emailVerified: r
|
|
248
|
-
name: r
|
|
249
|
-
metadata: { avatar_url: r
|
|
247
|
+
id: typeof r["sub"] === "string" ? r["sub"] : "",
|
|
248
|
+
...typeof r["email"] === "string" ? { email: r["email"] } : {},
|
|
249
|
+
emailVerified: r["email_verified"] === true,
|
|
250
|
+
...typeof r["name"] === "string" ? { name: r["name"] } : {},
|
|
251
|
+
metadata: { avatar_url: r["picture"], full_name: r["name"] }
|
|
250
252
|
})
|
|
251
253
|
},
|
|
252
254
|
github: {
|
|
@@ -254,7 +256,12 @@ var PRESETS = {
|
|
|
254
256
|
tokenUrl: "https://github.com/login/oauth/access_token",
|
|
255
257
|
userInfoUrl: "https://api.github.com/user",
|
|
256
258
|
scopes: "read:user user:email",
|
|
257
|
-
profileMap: (r) => ({
|
|
259
|
+
profileMap: (r) => ({
|
|
260
|
+
id: String(r["id"]),
|
|
261
|
+
...typeof r["email"] === "string" ? { email: r["email"] } : {},
|
|
262
|
+
...typeof r["name"] === "string" || typeof r["login"] === "string" ? { name: typeof r["name"] === "string" ? r["name"] : r["login"] } : {},
|
|
263
|
+
metadata: { avatar_url: r["avatar_url"], user_name: r["login"] }
|
|
264
|
+
})
|
|
258
265
|
}
|
|
259
266
|
};
|
|
260
267
|
function resolveProvider(name, cfg) {
|
|
@@ -273,7 +280,11 @@ function resolveProvider(name, cfg) {
|
|
|
273
280
|
tokenUrl,
|
|
274
281
|
userInfoUrl,
|
|
275
282
|
scopes: cfg.scopes ?? preset.scopes ?? "",
|
|
276
|
-
profileMap: cfg.profileMap ?? preset.profileMap ?? ((r) => ({
|
|
283
|
+
profileMap: cfg.profileMap ?? preset.profileMap ?? ((r) => ({
|
|
284
|
+
id: String(r["id"] ?? r["sub"]),
|
|
285
|
+
...typeof r["email"] === "string" ? { email: r["email"] } : {},
|
|
286
|
+
...typeof r["name"] === "string" ? { name: r["name"] } : {}
|
|
287
|
+
}))
|
|
277
288
|
};
|
|
278
289
|
}
|
|
279
290
|
|
|
@@ -969,41 +980,41 @@ var DEFAULT_AUTH_SETTINGS = {
|
|
|
969
980
|
};
|
|
970
981
|
function sanitize(raw) {
|
|
971
982
|
const s = { ...DEFAULT_AUTH_SETTINGS };
|
|
972
|
-
if (typeof raw
|
|
973
|
-
s.disableSignup = raw
|
|
974
|
-
if (typeof raw
|
|
975
|
-
s.anonymousUsers = raw
|
|
976
|
-
if (typeof raw
|
|
977
|
-
s.autoconfirm = raw
|
|
978
|
-
if (typeof raw
|
|
979
|
-
s.minPasswordLength = Math.max(4, Math.min(72, Math.floor(raw
|
|
980
|
-
}
|
|
981
|
-
if (Array.isArray(raw
|
|
982
|
-
s.disabledProviders = raw
|
|
983
|
-
}
|
|
984
|
-
if (typeof raw
|
|
985
|
-
s.otpLength = Math.max(6, Math.min(10, Math.floor(raw
|
|
986
|
-
}
|
|
987
|
-
if (typeof raw
|
|
988
|
-
s.otpExpirySeconds = Math.floor(raw
|
|
989
|
-
}
|
|
990
|
-
if (typeof raw
|
|
991
|
-
s.smsEnabled = raw
|
|
992
|
-
if (typeof raw
|
|
993
|
-
s.smsSignupEnabled = raw
|
|
994
|
-
if (typeof raw
|
|
995
|
-
s.smsOtpCooldownSeconds = Math.max(0, Math.min(86400, Math.floor(raw
|
|
996
|
-
}
|
|
997
|
-
if (typeof raw
|
|
998
|
-
s.smsTemplate = raw
|
|
999
|
-
}
|
|
1000
|
-
if (typeof raw
|
|
1001
|
-
s.maxEnrolledFactors = Math.floor(raw
|
|
1002
|
-
}
|
|
1003
|
-
if (typeof raw
|
|
1004
|
-
s.totpEnrollEnabled = raw
|
|
1005
|
-
if (typeof raw
|
|
1006
|
-
s.totpVerifyEnabled = raw
|
|
983
|
+
if (typeof raw["disableSignup"] === "boolean")
|
|
984
|
+
s.disableSignup = raw["disableSignup"];
|
|
985
|
+
if (typeof raw["anonymousUsers"] === "boolean")
|
|
986
|
+
s.anonymousUsers = raw["anonymousUsers"];
|
|
987
|
+
if (typeof raw["autoconfirm"] === "boolean")
|
|
988
|
+
s.autoconfirm = raw["autoconfirm"];
|
|
989
|
+
if (typeof raw["minPasswordLength"] === "number" && Number.isFinite(raw["minPasswordLength"])) {
|
|
990
|
+
s.minPasswordLength = Math.max(4, Math.min(72, Math.floor(raw["minPasswordLength"])));
|
|
991
|
+
}
|
|
992
|
+
if (Array.isArray(raw["disabledProviders"])) {
|
|
993
|
+
s.disabledProviders = raw["disabledProviders"].filter((p) => typeof p === "string").slice(0, 50);
|
|
994
|
+
}
|
|
995
|
+
if (typeof raw["otpLength"] === "number" && Number.isFinite(raw["otpLength"])) {
|
|
996
|
+
s.otpLength = Math.max(6, Math.min(10, Math.floor(raw["otpLength"])));
|
|
997
|
+
}
|
|
998
|
+
if (typeof raw["otpExpirySeconds"] === "number" && Number.isFinite(raw["otpExpirySeconds"]) && raw["otpExpirySeconds"] > 0) {
|
|
999
|
+
s.otpExpirySeconds = Math.floor(raw["otpExpirySeconds"]);
|
|
1000
|
+
}
|
|
1001
|
+
if (typeof raw["smsEnabled"] === "boolean")
|
|
1002
|
+
s.smsEnabled = raw["smsEnabled"];
|
|
1003
|
+
if (typeof raw["smsSignupEnabled"] === "boolean")
|
|
1004
|
+
s.smsSignupEnabled = raw["smsSignupEnabled"];
|
|
1005
|
+
if (typeof raw["smsOtpCooldownSeconds"] === "number" && Number.isFinite(raw["smsOtpCooldownSeconds"])) {
|
|
1006
|
+
s.smsOtpCooldownSeconds = Math.max(0, Math.min(86400, Math.floor(raw["smsOtpCooldownSeconds"])));
|
|
1007
|
+
}
|
|
1008
|
+
if (typeof raw["smsTemplate"] === "string" && raw["smsTemplate"].length > 0 && raw["smsTemplate"].length <= 1000 && /\{\{\s*\.Code\s*\}\}/.test(raw["smsTemplate"])) {
|
|
1009
|
+
s.smsTemplate = raw["smsTemplate"];
|
|
1010
|
+
}
|
|
1011
|
+
if (typeof raw["maxEnrolledFactors"] === "number" && Number.isFinite(raw["maxEnrolledFactors"]) && raw["maxEnrolledFactors"] > 0) {
|
|
1012
|
+
s.maxEnrolledFactors = Math.floor(raw["maxEnrolledFactors"]);
|
|
1013
|
+
}
|
|
1014
|
+
if (typeof raw["totpEnrollEnabled"] === "boolean")
|
|
1015
|
+
s.totpEnrollEnabled = raw["totpEnrollEnabled"];
|
|
1016
|
+
if (typeof raw["totpVerifyEnabled"] === "boolean")
|
|
1017
|
+
s.totpVerifyEnabled = raw["totpVerifyEnabled"];
|
|
1007
1018
|
return s;
|
|
1008
1019
|
}
|
|
1009
1020
|
async function loadAuthSettings(db, defaults = {}) {
|
|
@@ -1044,7 +1055,7 @@ class RateLimiter {
|
|
|
1044
1055
|
const cutoff = now - rule.windowMs;
|
|
1045
1056
|
const recent = (this.hits.get(bucketKey) ?? []).filter((t) => t > cutoff);
|
|
1046
1057
|
if (recent.length >= rule.limit) {
|
|
1047
|
-
const retryMs = recent[0] + rule.windowMs - now;
|
|
1058
|
+
const retryMs = (recent[0] ?? now) + rule.windowMs - now;
|
|
1048
1059
|
return Math.max(1, Math.ceil(retryMs / 1000));
|
|
1049
1060
|
}
|
|
1050
1061
|
recent.push(now);
|
|
@@ -1876,13 +1887,14 @@ Or sign in with this link: ${link}`
|
|
|
1876
1887
|
const body = await req.json().catch(() => ({}));
|
|
1877
1888
|
if (!body.email)
|
|
1878
1889
|
return authError(400, "validation_failed", "email is required");
|
|
1890
|
+
const email = body.email;
|
|
1879
1891
|
const hashed = body.password ? await hashPassword(body.password) : null;
|
|
1880
1892
|
const created = await this.db.transaction(async (query) => {
|
|
1881
1893
|
const res = await query(`insert into auth.users
|
|
1882
1894
|
(aud, role, email, encrypted_password, email_confirmed_at, raw_app_meta_data, raw_user_meta_data)
|
|
1883
1895
|
values ('authenticated', 'authenticated', $1, $2, case when $3 then now() else null end, $4, $5)
|
|
1884
1896
|
returning *`, [
|
|
1885
|
-
|
|
1897
|
+
email.toLowerCase().trim(),
|
|
1886
1898
|
hashed,
|
|
1887
1899
|
body.email_confirm ?? true,
|
|
1888
1900
|
JSON.stringify({ provider: "email", providers: ["email"], ...body.app_metadata ?? {} }),
|
|
@@ -2545,11 +2557,11 @@ function installDenoShim() {
|
|
|
2545
2557
|
}
|
|
2546
2558
|
function takeCapturedHandler() {
|
|
2547
2559
|
const h = captured.handler;
|
|
2548
|
-
captured.handler
|
|
2560
|
+
delete captured.handler;
|
|
2549
2561
|
return h;
|
|
2550
2562
|
}
|
|
2551
2563
|
function resetCapturedHandler() {
|
|
2552
|
-
captured.handler
|
|
2564
|
+
delete captured.handler;
|
|
2553
2565
|
}
|
|
2554
2566
|
|
|
2555
2567
|
// src/runtime/functions/edge-runtime-shim.ts
|
|
@@ -2573,7 +2585,7 @@ function installEdgeRuntimeShim() {
|
|
|
2573
2585
|
}
|
|
2574
2586
|
};
|
|
2575
2587
|
installed = runtime;
|
|
2576
|
-
globalThis
|
|
2588
|
+
globalThis["EdgeRuntime"] = runtime;
|
|
2577
2589
|
}
|
|
2578
2590
|
function runWithBackgroundTasks(options, fn) {
|
|
2579
2591
|
installEdgeRuntimeShim();
|
|
@@ -2945,9 +2957,13 @@ class FunctionsHandler {
|
|
|
2945
2957
|
const env = capabilities?.secrets ? filterSecretsEnv(this.env, capabilities.secrets) : this.env;
|
|
2946
2958
|
const invoke = () => this.invoke(entry.handler, request, ctx, env);
|
|
2947
2959
|
const inner = () => runWithDenoEnv(env, () => runWithPgredisCache(this.pgredis, invoke));
|
|
2948
|
-
const
|
|
2949
|
-
const
|
|
2950
|
-
const
|
|
2960
|
+
const outboundHosts = capabilities?.outboundHosts;
|
|
2961
|
+
const run = outboundHosts ? () => runWithFetchPolicy(outboundHosts, inner) : inner;
|
|
2962
|
+
const invokeWithBackground = () => runWithBackgroundTasks({
|
|
2963
|
+
allowed: capabilities?.background !== false,
|
|
2964
|
+
...limits?.waitUntilTimeoutMs === undefined ? {} : { timeoutMs: limits.waitUntilTimeoutMs }
|
|
2965
|
+
}, run);
|
|
2966
|
+
const res = timeoutMs !== undefined && abort !== undefined ? await this.withTimeout(name, timeoutMs, invokeWithBackground, abort) : await invokeWithBackground();
|
|
2951
2967
|
if (!(res instanceof Response)) {
|
|
2952
2968
|
return json2(500, { error: `function "${name}" did not return a Response` });
|
|
2953
2969
|
}
|
|
@@ -3015,6 +3031,8 @@ function withCountedBody(req, limit) {
|
|
|
3015
3031
|
controller.enqueue(chunk);
|
|
3016
3032
|
}
|
|
3017
3033
|
});
|
|
3034
|
+
if (!req.body)
|
|
3035
|
+
return new Request(req);
|
|
3018
3036
|
return new Request(req, { body: req.body.pipeThrough(counter), duplex: "half" });
|
|
3019
3037
|
}
|
|
3020
3038
|
function withResponseLimit(name, res, limit) {
|
|
@@ -6362,7 +6380,7 @@ async function createPgliteEngine(dataDir, options = {}) {
|
|
|
6362
6380
|
let pg;
|
|
6363
6381
|
try {
|
|
6364
6382
|
pg = new PGlite({
|
|
6365
|
-
dataDir,
|
|
6383
|
+
...dataDir === undefined ? {} : { dataDir },
|
|
6366
6384
|
extensions,
|
|
6367
6385
|
...standaloneAssets ? {
|
|
6368
6386
|
pgliteWasmModule: standaloneAssets.pgliteWasmModule,
|
|
@@ -6391,7 +6409,7 @@ async function createPgliteEngine(dataDir, options = {}) {
|
|
|
6391
6409
|
return {
|
|
6392
6410
|
async query(sql, params) {
|
|
6393
6411
|
const res = await pg.query(sql, params);
|
|
6394
|
-
return { rows: res.rows, affectedRows: res.affectedRows };
|
|
6412
|
+
return { rows: res.rows, ...res.affectedRows === undefined ? {} : { affectedRows: res.affectedRows } };
|
|
6395
6413
|
},
|
|
6396
6414
|
async exec(sql) {
|
|
6397
6415
|
await pg.exec(sql);
|
|
@@ -6401,7 +6419,7 @@ async function createPgliteEngine(dataDir, options = {}) {
|
|
|
6401
6419
|
return fn({
|
|
6402
6420
|
async query(sql, params) {
|
|
6403
6421
|
const res = await tx.query(sql, params);
|
|
6404
|
-
return { rows: res.rows, affectedRows: res.affectedRows };
|
|
6422
|
+
return { rows: res.rows, ...res.affectedRows === undefined ? {} : { affectedRows: res.affectedRows } };
|
|
6405
6423
|
},
|
|
6406
6424
|
async exec(sql) {
|
|
6407
6425
|
await tx.exec(sql);
|
|
@@ -6430,8 +6448,8 @@ function getStandaloneAssets() {
|
|
|
6430
6448
|
function withEmbeddedBundle(extension, bundlePath) {
|
|
6431
6449
|
return {
|
|
6432
6450
|
...extension,
|
|
6433
|
-
setup: async (
|
|
6434
|
-
...await extension.setup(
|
|
6451
|
+
setup: async (...args) => ({
|
|
6452
|
+
...await extension.setup(...args),
|
|
6435
6453
|
bundlePath
|
|
6436
6454
|
})
|
|
6437
6455
|
};
|
|
@@ -6655,12 +6673,17 @@ class Database {
|
|
|
6655
6673
|
order by kcu.constraint_name, kcu.ordinal_position`, [schema]);
|
|
6656
6674
|
const uniqByTable = new Map;
|
|
6657
6675
|
for (const u of uniq.rows) {
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
byConstraint.get(u.constraint_name)
|
|
6676
|
+
let byConstraint = uniqByTable.get(u.table_name);
|
|
6677
|
+
if (!byConstraint) {
|
|
6678
|
+
byConstraint = new Map;
|
|
6679
|
+
uniqByTable.set(u.table_name, byConstraint);
|
|
6680
|
+
}
|
|
6681
|
+
let columns = byConstraint.get(u.constraint_name);
|
|
6682
|
+
if (!columns) {
|
|
6683
|
+
columns = [];
|
|
6684
|
+
byConstraint.set(u.constraint_name, columns);
|
|
6685
|
+
}
|
|
6686
|
+
columns.push(u.column_name);
|
|
6664
6687
|
}
|
|
6665
6688
|
const fks = await this.engine.query(`select
|
|
6666
6689
|
tc.constraint_name,
|
|
@@ -6681,22 +6704,27 @@ class Database {
|
|
|
6681
6704
|
order by tc.constraint_name, kcu.ordinal_position`, [schema]);
|
|
6682
6705
|
const pkSet = new Map;
|
|
6683
6706
|
for (const pk of pks.rows) {
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6707
|
+
let columns = pkSet.get(pk.table_name);
|
|
6708
|
+
if (!columns) {
|
|
6709
|
+
columns = new Set;
|
|
6710
|
+
pkSet.set(pk.table_name, columns);
|
|
6711
|
+
}
|
|
6712
|
+
columns.add(pk.column_name);
|
|
6687
6713
|
}
|
|
6688
6714
|
const tables = new Map;
|
|
6689
6715
|
for (const c of cols.rows) {
|
|
6690
|
-
|
|
6691
|
-
|
|
6716
|
+
let table = tables.get(c.table_name);
|
|
6717
|
+
if (!table) {
|
|
6718
|
+
table = {
|
|
6692
6719
|
schema,
|
|
6693
6720
|
name: c.table_name,
|
|
6694
6721
|
columns: [],
|
|
6695
6722
|
primaryKey: [...pkSet.get(c.table_name) ?? []],
|
|
6696
6723
|
uniqueKeys: [...uniqByTable.get(c.table_name)?.values() ?? []]
|
|
6697
|
-
}
|
|
6724
|
+
};
|
|
6725
|
+
tables.set(c.table_name, table);
|
|
6698
6726
|
}
|
|
6699
|
-
|
|
6727
|
+
table.columns.push({
|
|
6700
6728
|
name: c.column_name,
|
|
6701
6729
|
udtName: c.udt_name,
|
|
6702
6730
|
isNullable: c.is_nullable === "YES",
|
|
@@ -6868,12 +6896,15 @@ function parseIdentityArgs(identity) {
|
|
|
6868
6896
|
parts.push(current);
|
|
6869
6897
|
return parts.map((part) => {
|
|
6870
6898
|
const tokens = part.trim().split(/\s+/);
|
|
6871
|
-
while (tokens.length > 1 && ["IN", "OUT", "INOUT", "VARIADIC"].includes(tokens[0])) {
|
|
6899
|
+
while (tokens.length > 1 && ["IN", "OUT", "INOUT", "VARIADIC"].includes(tokens[0] ?? "")) {
|
|
6872
6900
|
tokens.shift();
|
|
6873
6901
|
}
|
|
6902
|
+
const first = tokens[0];
|
|
6903
|
+
if (first === undefined || first === "")
|
|
6904
|
+
throw new Error("invalid function identity argument");
|
|
6874
6905
|
if (tokens.length === 1)
|
|
6875
|
-
return { name: "", type:
|
|
6876
|
-
const name =
|
|
6906
|
+
return { name: "", type: first };
|
|
6907
|
+
const name = first.replace(/^"|"$/g, "");
|
|
6877
6908
|
return { name, type: tokens.slice(1).join(" ") };
|
|
6878
6909
|
});
|
|
6879
6910
|
}
|
|
@@ -7185,6 +7216,8 @@ class RealtimeEngine {
|
|
|
7185
7216
|
const state = this.presence.get(topic);
|
|
7186
7217
|
if (state?.has(channel.presenceKey)) {
|
|
7187
7218
|
const metas = state.get(channel.presenceKey);
|
|
7219
|
+
if (!metas)
|
|
7220
|
+
return;
|
|
7188
7221
|
state.delete(channel.presenceKey);
|
|
7189
7222
|
if (state.size === 0)
|
|
7190
7223
|
this.presence.delete(topic);
|
|
@@ -7479,7 +7512,7 @@ class ApiError extends Error {
|
|
|
7479
7512
|
status;
|
|
7480
7513
|
body;
|
|
7481
7514
|
constructor(status, body) {
|
|
7482
|
-
super(typeof body
|
|
7515
|
+
super(typeof body["message"] === "string" ? body["message"] : JSON.stringify(body));
|
|
7483
7516
|
this.status = status;
|
|
7484
7517
|
this.body = body;
|
|
7485
7518
|
}
|
|
@@ -7857,6 +7890,8 @@ class QueryBuilder {
|
|
|
7857
7890
|
const cells = columns.map((c) => {
|
|
7858
7891
|
if (!(c in row)) {
|
|
7859
7892
|
const col = tinfo.columns.find((x) => x.name === c);
|
|
7893
|
+
if (!col)
|
|
7894
|
+
throw new ApiError(400, { code: "PGRST204", message: `Column ${c} does not exist` });
|
|
7860
7895
|
return opts.missingDefault && col.hasDefault ? "default" : "null";
|
|
7861
7896
|
}
|
|
7862
7897
|
return this.pushParam(params, tinfo, c, row[c]);
|
|
@@ -7976,6 +8011,8 @@ class QueryBuilder {
|
|
|
7976
8011
|
});
|
|
7977
8012
|
} else {
|
|
7978
8013
|
const j = rel.junction;
|
|
8014
|
+
if (!j)
|
|
8015
|
+
throw new Error("many-to-many relationship is missing its junction table");
|
|
7979
8016
|
const jAlias = this.nextAlias();
|
|
7980
8017
|
const joinConds = j.fkToTarget.srcColumns.map((src, i) => `${quoteIdent(jAlias)}.${quoteIdent(src)} = ${quoteIdent(childAlias)}.${quoteIdent(j.fkToTarget.tgtColumns[i])}`);
|
|
7981
8018
|
fromClause = `${this.qualify(rel.targetTable)} as ${quoteIdent(childAlias)} join ${this.qualify(j.table)} as ${quoteIdent(jAlias)} on ${joinConds.join(" and ")}`;
|
|
@@ -8241,6 +8278,8 @@ function renderFilter(f, alias) {
|
|
|
8241
8278
|
case "phfts":
|
|
8242
8279
|
case "wfts": {
|
|
8243
8280
|
const fn = { fts: "to_tsquery", plfts: "plainto_tsquery", phfts: "phraseto_tsquery", wfts: "websearch_to_tsquery" }[f.op];
|
|
8281
|
+
if (!fn)
|
|
8282
|
+
throw new Error(`Unsupported full-text operator: ${f.op}`);
|
|
8244
8283
|
expr = f.ftsConfig ? `${colExpr} @@ ${fn}(${quoteLiteral(f.ftsConfig)}, ${quoteLiteral(f.value)})` : `${colExpr} @@ ${fn}(${quoteLiteral(f.value)})`;
|
|
8245
8284
|
break;
|
|
8246
8285
|
}
|
|
@@ -8535,9 +8574,10 @@ class RestHandler {
|
|
|
8535
8574
|
if (!opts.returning) {
|
|
8536
8575
|
return new Response(null, { status: opts.status === 201 ? 201 : 204, headers: countHeader });
|
|
8537
8576
|
}
|
|
8538
|
-
|
|
8577
|
+
const responseRows = rows ?? [];
|
|
8578
|
+
return this.dataResponse(responseRows, {
|
|
8539
8579
|
status: opts.status,
|
|
8540
|
-
count: opts.prefer.count !== undefined ?
|
|
8580
|
+
count: opts.prefer.count !== undefined ? responseRows.length : null,
|
|
8541
8581
|
offset: 0,
|
|
8542
8582
|
wantsObject: opts.wantsObject,
|
|
8543
8583
|
wantsCsv: opts.wantsCsv,
|
|
@@ -8648,7 +8688,7 @@ class RestHandler {
|
|
|
8648
8688
|
if (item.name === "*") {
|
|
8649
8689
|
exprs.push(`${quoteIdent(alias)}.*`);
|
|
8650
8690
|
} else {
|
|
8651
|
-
const out = item.alias ?? item.name.split(/->>|->/).pop().trim();
|
|
8691
|
+
const out = item.alias ?? (item.name.split(/->>|->/).pop() ?? item.name).trim();
|
|
8652
8692
|
const cast = item.cast ? `::${sanitizeCast(item.cast)}` : "";
|
|
8653
8693
|
exprs.push(`${renderColumnExpr(alias, item.name)}${cast} as ${quoteIdent(out)}`);
|
|
8654
8694
|
}
|
|
@@ -8902,7 +8942,10 @@ function applyResize(image, metadata, options) {
|
|
|
8902
8942
|
image.resize(options.width);
|
|
8903
8943
|
return;
|
|
8904
8944
|
}
|
|
8905
|
-
const
|
|
8945
|
+
const height = options.height;
|
|
8946
|
+
if (height === undefined)
|
|
8947
|
+
return;
|
|
8948
|
+
const proportionalWidth = Math.max(1, Math.round(metadata.width * height / metadata.height));
|
|
8906
8949
|
image.resize(proportionalWidth);
|
|
8907
8950
|
}
|
|
8908
8951
|
async function transformImage(source, options, knownSourceSize) {
|
|
@@ -9059,6 +9102,8 @@ class ImageTransformCache {
|
|
|
9059
9102
|
if (oldestKey === undefined)
|
|
9060
9103
|
break;
|
|
9061
9104
|
const oldest = this.entries.get(oldestKey);
|
|
9105
|
+
if (!oldest)
|
|
9106
|
+
break;
|
|
9062
9107
|
this.entries.delete(oldestKey);
|
|
9063
9108
|
this.cachedBytes -= oldest.size;
|
|
9064
9109
|
}
|
|
@@ -9908,8 +9953,9 @@ class StorageHandler {
|
|
|
9908
9953
|
...[...folders].map((name) => ({ name, row: null })),
|
|
9909
9954
|
...Object.entries(files).map(([name, row]) => ({ name, row }))
|
|
9910
9955
|
];
|
|
9911
|
-
|
|
9912
|
-
|
|
9956
|
+
const search = body.search;
|
|
9957
|
+
if (search)
|
|
9958
|
+
entries = entries.filter((e) => e.name.includes(search));
|
|
9913
9959
|
const column = body.sortBy?.column ?? "name";
|
|
9914
9960
|
const asc = (body.sortBy?.order ?? "asc") === "asc";
|
|
9915
9961
|
entries.sort((a, b) => {
|
|
@@ -10059,6 +10105,8 @@ function parseSizeLimit(v) {
|
|
|
10059
10105
|
if (!m)
|
|
10060
10106
|
throw new StorageValidationError(`invalid file_size_limit: ${v}`);
|
|
10061
10107
|
const mult = { b: 1, kb: 1024, mb: 1024 ** 2, gb: 1024 ** 3 }[(m[2] ?? "b").toLowerCase()];
|
|
10108
|
+
if (mult === undefined)
|
|
10109
|
+
throw new StorageValidationError(`invalid file_size_limit: ${v}`);
|
|
10062
10110
|
return Math.floor(parseFloat(m[1]) * mult);
|
|
10063
10111
|
}
|
|
10064
10112
|
function objectMetadata(size, contentType, cacheControl) {
|
|
@@ -10357,7 +10405,7 @@ class NetService {
|
|
|
10357
10405
|
const res = await guardedFetch(this.fetchImpl, row.url, {
|
|
10358
10406
|
method: row.method,
|
|
10359
10407
|
headers,
|
|
10360
|
-
body
|
|
10408
|
+
...hasBody && row.body !== null ? { body: row.body } : {},
|
|
10361
10409
|
signal: controller.signal
|
|
10362
10410
|
});
|
|
10363
10411
|
status = res.status;
|
|
@@ -10376,9 +10424,9 @@ class NetService {
|
|
|
10376
10424
|
id: row.id,
|
|
10377
10425
|
method: row.method,
|
|
10378
10426
|
url: row.url,
|
|
10379
|
-
status: status
|
|
10427
|
+
...status === null ? {} : { status },
|
|
10380
10428
|
timedOut,
|
|
10381
|
-
error: errorMsg
|
|
10429
|
+
...errorMsg === null ? {} : { error: errorMsg }
|
|
10382
10430
|
});
|
|
10383
10431
|
}
|
|
10384
10432
|
async record(id, status, contentType, respHeaders, content, timedOut, errorMsg) {
|
|
@@ -10831,16 +10879,16 @@ class GraphqlHandler {
|
|
|
10831
10879
|
throw new ApiError(415, { message: "GraphQL requests require application/json" });
|
|
10832
10880
|
}
|
|
10833
10881
|
const body = JSON.parse(await boundedBody(request, this.maxBytes));
|
|
10834
|
-
if (!isObject(body) || typeof body
|
|
10882
|
+
if (!isObject(body) || typeof body["query"] !== "string" || !body["query"].trim() || body["variables"] != null && !isObject(body["variables"]) || body["extensions"] != null && !isObject(body["extensions"]) || body["operationName"] != null && typeof body["operationName"] !== "string") {
|
|
10835
10883
|
throw new ApiError(400, { message: "Invalid GraphQL request" });
|
|
10836
10884
|
}
|
|
10837
|
-
operationName = body
|
|
10885
|
+
operationName = body["operationName"] ?? undefined;
|
|
10838
10886
|
const result = await this.db.withContext(context, async (query) => {
|
|
10839
10887
|
await query(`select set_config('statement_timeout', $1, true)`, [String(this.timeoutMs)]);
|
|
10840
10888
|
await query(`select set_config('search_path', $1, true)`, [
|
|
10841
10889
|
this.schemas.map((schema) => `"${schema.replaceAll('"', '""')}"`).join(",")
|
|
10842
10890
|
]);
|
|
10843
|
-
return query("select graphql.resolve($1::text, $2::jsonb, $3::text, $4::jsonb) as result", [body
|
|
10891
|
+
return query("select graphql.resolve($1::text, $2::jsonb, $3::text, $4::jsonb) as result", [body["query"], JSON.stringify(body["variables"] ?? {}), body["operationName"] ?? null, JSON.stringify(body["extensions"] ?? {})]);
|
|
10844
10892
|
});
|
|
10845
10893
|
const row = result.rows[0];
|
|
10846
10894
|
if (result.rows.length !== 1 || !isRecord(row) || !isGraphqlEnvelope(row["result"])) {
|
|
@@ -10938,9 +10986,12 @@ async function generateTypes(db, schema = "public") {
|
|
|
10938
10986
|
const views = new Map;
|
|
10939
10987
|
for (const c of cols) {
|
|
10940
10988
|
const target = c.table_kind === "r" ? tables : views;
|
|
10941
|
-
|
|
10942
|
-
|
|
10943
|
-
|
|
10989
|
+
let columns = target.get(c.table_name);
|
|
10990
|
+
if (!columns) {
|
|
10991
|
+
columns = [];
|
|
10992
|
+
target.set(c.table_name, columns);
|
|
10993
|
+
}
|
|
10994
|
+
columns.push(c);
|
|
10944
10995
|
}
|
|
10945
10996
|
const tsType = (udt, nullable) => {
|
|
10946
10997
|
const base = mapType(udt, enumNames, schema);
|
|
@@ -11182,9 +11233,12 @@ async function snapshotSchema(db, schema = "public") {
|
|
|
11182
11233
|
const constraints = new Map;
|
|
11183
11234
|
const constraintIndexOids = new Set;
|
|
11184
11235
|
for (const c of cons.rows) {
|
|
11185
|
-
|
|
11186
|
-
|
|
11187
|
-
|
|
11236
|
+
let tableConstraints = constraints.get(c.table);
|
|
11237
|
+
if (!tableConstraints) {
|
|
11238
|
+
tableConstraints = new Map;
|
|
11239
|
+
constraints.set(c.table, tableConstraints);
|
|
11240
|
+
}
|
|
11241
|
+
tableConstraints.set(c.name, c.def);
|
|
11188
11242
|
if (c.conindid)
|
|
11189
11243
|
constraintIndexOids.add(c.conindid);
|
|
11190
11244
|
}
|
|
@@ -11198,9 +11252,12 @@ async function snapshotSchema(db, schema = "public") {
|
|
|
11198
11252
|
for (const r of idx.rows) {
|
|
11199
11253
|
if (constraintIndexOids.has(r.indexrelid))
|
|
11200
11254
|
continue;
|
|
11201
|
-
|
|
11202
|
-
|
|
11203
|
-
|
|
11255
|
+
let tableIndexes = indexes.get(r.table);
|
|
11256
|
+
if (!tableIndexes) {
|
|
11257
|
+
tableIndexes = new Map;
|
|
11258
|
+
indexes.set(r.table, tableIndexes);
|
|
11259
|
+
}
|
|
11260
|
+
tableIndexes.set(r.name, r.def);
|
|
11204
11261
|
}
|
|
11205
11262
|
const en = await db.query(`select t.typname as name, array_agg(e.enumlabel order by e.enumsortorder) as labels
|
|
11206
11263
|
from pg_type t join pg_enum e on e.enumtypid = t.oid
|
|
@@ -11299,7 +11356,7 @@ function schemasEqual(left, right) {
|
|
|
11299
11356
|
if (value instanceof Map)
|
|
11300
11357
|
return [...value].sort(([a], [b]) => String(a).localeCompare(String(b))).map(([key, entry]) => [key, canonical(entry)]);
|
|
11301
11358
|
if (Array.isArray(value))
|
|
11302
|
-
return value.map(canonical);
|
|
11359
|
+
return value.map((entry) => canonical(entry));
|
|
11303
11360
|
if (value !== null && typeof value === "object")
|
|
11304
11361
|
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => [key, canonical(entry)]));
|
|
11305
11362
|
return value;
|
|
@@ -11911,7 +11968,11 @@ async function revokeReplicationGrants(tx) {
|
|
|
11911
11968
|
}
|
|
11912
11969
|
}
|
|
11913
11970
|
async function grantReplicationAllowlist(tx, tables) {
|
|
11914
|
-
const schemas = uniqueSorted(tables.map((table) =>
|
|
11971
|
+
const schemas = uniqueSorted(tables.map((table) => {
|
|
11972
|
+
if (!table.schema)
|
|
11973
|
+
throw new Error(`publication table ${table.name} has no schema`);
|
|
11974
|
+
return table.schema;
|
|
11975
|
+
}));
|
|
11915
11976
|
for (const schema of schemas) {
|
|
11916
11977
|
await tx.exec(`GRANT USAGE ON SCHEMA ${quoteIdentifier(schema)} TO ${POWERSYNC_REPLICATION_ROLE}`);
|
|
11917
11978
|
}
|
|
@@ -12015,6 +12076,8 @@ function normalizeCidr(cidr) {
|
|
|
12015
12076
|
if (!match)
|
|
12016
12077
|
throw new Error(`invalid PowerSync client CIDR: ${cidr}`);
|
|
12017
12078
|
const address = match[1];
|
|
12079
|
+
if (!address)
|
|
12080
|
+
throw new Error(`invalid PowerSync client CIDR: ${cidr}`);
|
|
12018
12081
|
const family = isIP(address);
|
|
12019
12082
|
const prefix = Number(match[2]);
|
|
12020
12083
|
if (family === 0 || prefix < 0 || prefix > (family === 4 ? 32 : 128)) {
|
|
@@ -12121,12 +12184,12 @@ class PgWireClient {
|
|
|
12121
12184
|
if (code === 0) {} else if (code === 3) {
|
|
12122
12185
|
if (!needPassword())
|
|
12123
12186
|
return;
|
|
12124
|
-
this.socket.write(message(112, cstring(opts.password)));
|
|
12187
|
+
this.socket.write(message(112, cstring(opts.password ?? "")));
|
|
12125
12188
|
} else if (code === 5) {
|
|
12126
12189
|
if (!needPassword())
|
|
12127
12190
|
return;
|
|
12128
12191
|
const salt = payload.subarray(4, 8);
|
|
12129
|
-
const inner = md5Hex(Buffer.from(opts.password + opts.user, "utf8"));
|
|
12192
|
+
const inner = md5Hex(Buffer.from((opts.password ?? "") + opts.user, "utf8"));
|
|
12130
12193
|
const token = "md5" + md5Hex(Buffer.concat([Buffer.from(inner, "utf8"), salt]));
|
|
12131
12194
|
this.socket.write(message(112, cstring(token)));
|
|
12132
12195
|
} else if (code === 10) {
|
|
@@ -12148,6 +12211,10 @@ class PgWireClient {
|
|
|
12148
12211
|
reject(new Error("SCRAM: server nonce does not extend client nonce"));
|
|
12149
12212
|
return;
|
|
12150
12213
|
}
|
|
12214
|
+
if (!attrs.s || !attrs.i || !opts.password) {
|
|
12215
|
+
reject(new Error("SCRAM: incomplete server challenge"));
|
|
12216
|
+
return;
|
|
12217
|
+
}
|
|
12151
12218
|
const salt = Buffer.from(attrs.s, "base64");
|
|
12152
12219
|
const iterations = parseInt(attrs.i, 10);
|
|
12153
12220
|
const saltedPassword = pbkdf2Sync(opts.password, salt, iterations, 32, "sha256");
|
|
@@ -12564,7 +12631,7 @@ function toPgArrayLiteral(array) {
|
|
|
12564
12631
|
// src/runtime/node/native/engine.ts
|
|
12565
12632
|
var DEFAULT_PG_VERSION = "17.7.0";
|
|
12566
12633
|
var POSTGRES_MIRROR_URL_ERROR = "SUPACLOUD_LITE_POSTGRES_MIRROR must be an absolute HTTPS URL or a loopback HTTP URL";
|
|
12567
|
-
var NATIVE_POSTGRES_MAJOR = DEFAULT_PG_VERSION.split(".")[0];
|
|
12634
|
+
var NATIVE_POSTGRES_MAJOR = DEFAULT_PG_VERSION.split(".")[0] ?? "17";
|
|
12568
12635
|
function nativePostgresMajor(installDir = process.env.SUPACLOUD_LITE_POSTGRES_DIR) {
|
|
12569
12636
|
if (!installDir)
|
|
12570
12637
|
return NATIVE_POSTGRES_MAJOR;
|
|
@@ -12831,15 +12898,18 @@ ${detail}` : " (no output)"}
|
|
|
12831
12898
|
connect,
|
|
12832
12899
|
onClose: async () => {
|
|
12833
12900
|
removeExitHandler?.();
|
|
12834
|
-
|
|
12835
|
-
|
|
12901
|
+
if (postgres)
|
|
12902
|
+
await stopPostgres(postgres, () => postgresExited);
|
|
12903
|
+
if (socketDirectory)
|
|
12904
|
+
rmSync(socketDirectory, { recursive: true, force: true });
|
|
12836
12905
|
await releaseLock();
|
|
12837
12906
|
}
|
|
12838
12907
|
});
|
|
12839
12908
|
} catch (error) {
|
|
12840
12909
|
removeExitHandler?.();
|
|
12841
|
-
|
|
12842
|
-
|
|
12910
|
+
const processToStop = postgres;
|
|
12911
|
+
if (processToStop)
|
|
12912
|
+
await stopPostgres(processToStop, () => processToStop.exitCode !== null);
|
|
12843
12913
|
if (socketDirectory)
|
|
12844
12914
|
rmSync(socketDirectory, { recursive: true, force: true });
|
|
12845
12915
|
await releaseLock();
|
|
@@ -12923,6 +12993,8 @@ async function withDelta(opts, baseline, action) {
|
|
|
12923
12993
|
const liveSnap = await snapshotSchema(tx, schema);
|
|
12924
12994
|
const ddl = diffSchemas(shadowSnap, liveSnap, schema);
|
|
12925
12995
|
try {
|
|
12996
|
+
if (!shadow)
|
|
12997
|
+
throw new Error("Shadow database was closed before schema replay");
|
|
12926
12998
|
if (ddl.length)
|
|
12927
12999
|
await shadow.db.exec(ddl.join(`
|
|
12928
13000
|
`));
|
|
@@ -14526,7 +14598,10 @@ ${details}`, { cause: buildThrow });
|
|
|
14526
14598
|
throw new Error(buildOutput.logs.map((item) => item.message).join(`
|
|
14527
14599
|
`) || `failed to bundle ${entryPath}`);
|
|
14528
14600
|
}
|
|
14529
|
-
|
|
14601
|
+
const output = buildOutput.outputs[0];
|
|
14602
|
+
if (!output)
|
|
14603
|
+
throw new Error(`bundle produced no output for ${entryPath}`);
|
|
14604
|
+
return output.path;
|
|
14530
14605
|
} catch (buildError) {
|
|
14531
14606
|
try {
|
|
14532
14607
|
await rm3(outDir, { recursive: true, force: true });
|
|
@@ -14612,7 +14687,8 @@ async function loadFunctionsUnlocked(projectDir, options, mode) {
|
|
|
14612
14687
|
const dir = join7(root, name);
|
|
14613
14688
|
if (!(await stat2(dir)).isDirectory())
|
|
14614
14689
|
continue;
|
|
14615
|
-
const
|
|
14690
|
+
const entrypoint = options[name]?.entrypoint;
|
|
14691
|
+
const candidates = entrypoint ? [join7(projectDir, entrypoint)] : ["index.ts", "index.tsx", "index.js", "index.mjs"].map((f) => join7(dir, f));
|
|
14616
14692
|
for (const path of candidates) {
|
|
14617
14693
|
try {
|
|
14618
14694
|
await stat2(path);
|