@supacloud/lite 0.14.0 → 0.16.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 +27 -0
- package/THIRD_PARTY_NOTICES.md +4 -4
- package/dist/cli.js +441 -203
- package/dist/index.js +399 -162
- package/dist/launcher.cjs +1 -1
- package/dist/runtime/auth/oauth.d.ts.map +1 -1
- package/dist/runtime/auth/settings.d.ts.map +1 -1
- package/dist/runtime/db/emulated.d.ts +2 -2
- package/dist/runtime/db/emulated.d.ts.map +1 -1
- package/package.json +3 -3
- /package/LICENSES/{BUN-1.4.0-RUNTIME-NOTICES.txt → BUN-1.4.2-RUNTIME-NOTICES.txt} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
-
var __require = import.meta.require;
|
|
4
3
|
|
|
5
4
|
// src/cli.ts
|
|
6
5
|
import { existsSync as existsSync4 } from "fs";
|
|
@@ -9,7 +8,7 @@ import { dirname as dirname8, join as join10, resolve as resolve6 } from "path";
|
|
|
9
8
|
// package.json
|
|
10
9
|
var package_default = {
|
|
11
10
|
name: "@supacloud/lite",
|
|
12
|
-
version: "0.
|
|
11
|
+
version: "0.16.0",
|
|
13
12
|
description: "Bun-native, single-project Supabase-compatible backend powered by PGlite or native PostgreSQL",
|
|
14
13
|
type: "module",
|
|
15
14
|
license: "Apache-2.0",
|
|
@@ -69,13 +68,13 @@ var package_default = {
|
|
|
69
68
|
"@supacloud/compiler": "^0.13.1",
|
|
70
69
|
"@supacloud/elysia": "^0.10.0",
|
|
71
70
|
"@supabase/supabase-js": "^2.115.0",
|
|
72
|
-
"@types/bun": "^1.4.
|
|
71
|
+
"@types/bun": "^1.4.2",
|
|
73
72
|
elysia: "^1.4.30",
|
|
74
73
|
jose: "^6.2.11",
|
|
75
74
|
typescript: "^7.0.2"
|
|
76
75
|
},
|
|
77
76
|
engines: {
|
|
78
|
-
bun: ">=1.4.
|
|
77
|
+
bun: ">=1.4.2"
|
|
79
78
|
},
|
|
80
79
|
publishConfig: {
|
|
81
80
|
access: "public",
|
|
@@ -423,10 +422,15 @@ class OAuthService {
|
|
|
423
422
|
JSON.stringify({ provider, providers: [provider] }),
|
|
424
423
|
JSON.stringify({ sub: profile.id, email: profile.email, full_name: profile.name, ...profile.metadata })
|
|
425
424
|
]);
|
|
426
|
-
|
|
425
|
+
const user = created.rows[0];
|
|
426
|
+
if (!user)
|
|
427
|
+
throw new Error("OAuth user creation returned no row");
|
|
428
|
+
userId = user.id;
|
|
427
429
|
}
|
|
428
430
|
await this.db.query(`insert into auth.identities (user_id, provider, provider_id, identity_data)
|
|
429
431
|
values ($1, $2, $3, $4::jsonb)`, [userId, provider, profile.id, JSON.stringify({ sub: profile.id, email: profile.email, ...profile.metadata })]);
|
|
432
|
+
if (!userId)
|
|
433
|
+
throw new Error("OAuth identity has no user ID");
|
|
430
434
|
return userId;
|
|
431
435
|
}
|
|
432
436
|
}
|
|
@@ -639,10 +643,10 @@ class QrCode {
|
|
|
639
643
|
const bytes = new TextEncoder().encode(text);
|
|
640
644
|
let version = 1;
|
|
641
645
|
for (;version <= 40; version++) {
|
|
642
|
-
const
|
|
646
|
+
const dataCapacityBits = getNumDataCodewords(version) * 8;
|
|
643
647
|
const ccBits = version < 10 ? 8 : 16;
|
|
644
648
|
const usedBits = 4 + ccBits + bytes.length * 8;
|
|
645
|
-
if (usedBits <=
|
|
649
|
+
if (usedBits <= dataCapacityBits)
|
|
646
650
|
break;
|
|
647
651
|
}
|
|
648
652
|
if (version > 40)
|
|
@@ -1334,10 +1338,10 @@ class AuthHandler {
|
|
|
1334
1338
|
if (!this.settings.anonymousUsers) {
|
|
1335
1339
|
return authError(422, "anonymous_provider_disabled", "Anonymous sign-ins are disabled");
|
|
1336
1340
|
}
|
|
1337
|
-
const
|
|
1341
|
+
const res = await this.db.query(`insert into auth.users (aud, role, raw_app_meta_data, raw_user_meta_data, is_anonymous, last_sign_in_at)
|
|
1338
1342
|
values ('authenticated', 'authenticated', '{}', $1, true, now())
|
|
1339
1343
|
returning *`, [JSON.stringify(body.data ?? {})]);
|
|
1340
|
-
return json(200, await this.sessionFor(
|
|
1344
|
+
return json(200, await this.sessionFor(res.rows[0]));
|
|
1341
1345
|
}
|
|
1342
1346
|
if (this.settings.disableSignup) {
|
|
1343
1347
|
return authError(422, "signup_disabled", "Signups not allowed for this instance");
|
|
@@ -3931,45 +3935,202 @@ AS $$
|
|
|
3931
3935
|
DECLARE
|
|
3932
3936
|
normalized_queue_name text := lower(btrim(queue_name));
|
|
3933
3937
|
BEGIN
|
|
3934
|
-
IF normalized_queue_name
|
|
3935
|
-
OR left(normalized_queue_name, char_length('supacloud_internal_')) = 'supacloud_internal_' THEN
|
|
3938
|
+
IF left(normalized_queue_name, char_length('supacloud_internal_')) = 'supacloud_internal_' THEN
|
|
3936
3939
|
RAISE EXCEPTION 'SUPACLOUD_QUEUE_NAME_RESERVED' USING ERRCODE = '42501';
|
|
3937
3940
|
END IF;
|
|
3938
|
-
|
|
3941
|
+
IF queue_name IS NULL OR queue_name !~ '^[a-z0-9][a-z0-9_-]{0,127}$' THEN
|
|
3942
|
+
RAISE EXCEPTION 'SUPACLOUD_QUEUE_NAME_INVALID' USING ERRCODE = '22023';
|
|
3943
|
+
END IF;
|
|
3944
|
+
RETURN queue_name;
|
|
3945
|
+
END;
|
|
3946
|
+
$$;
|
|
3947
|
+
|
|
3948
|
+
-- Return-type changes require replacement. Do not CASCADE through user dependencies.
|
|
3949
|
+
DO $pgmq_receipt_types$
|
|
3950
|
+
BEGIN
|
|
3951
|
+
IF EXISTS (
|
|
3952
|
+
SELECT 1 FROM pg_catalog.pg_proc
|
|
3953
|
+
WHERE oid = to_regprocedure('pgmq_public.send(text,jsonb,integer)')
|
|
3954
|
+
AND prorettype = 'bigint'::regtype
|
|
3955
|
+
) THEN
|
|
3956
|
+
DROP FUNCTION pgmq_public.send(text,jsonb,integer);
|
|
3957
|
+
END IF;
|
|
3958
|
+
IF EXISTS (
|
|
3959
|
+
SELECT 1 FROM pg_catalog.pg_proc
|
|
3960
|
+
WHERE oid = to_regprocedure('pgmq_public.send_batch(text,jsonb[],integer)')
|
|
3961
|
+
AND prorettype = 'bigint'::regtype
|
|
3962
|
+
) THEN
|
|
3963
|
+
DROP FUNCTION pgmq_public.send_batch(text,jsonb[],integer);
|
|
3964
|
+
END IF;
|
|
3965
|
+
IF EXISTS (
|
|
3966
|
+
SELECT 1 FROM pg_catalog.pg_proc
|
|
3967
|
+
WHERE oid = to_regprocedure('pgmq_public.read(text,integer,integer)')
|
|
3968
|
+
AND prorettype = 'pgmq.message_record'::regtype
|
|
3969
|
+
) THEN
|
|
3970
|
+
DROP FUNCTION pgmq_public.read(text,integer,integer);
|
|
3971
|
+
END IF;
|
|
3972
|
+
IF EXISTS (
|
|
3973
|
+
SELECT 1 FROM pg_catalog.pg_proc
|
|
3974
|
+
WHERE oid = to_regprocedure('pgmq_public.pop(text)')
|
|
3975
|
+
AND prorettype = 'pgmq.message_record'::regtype
|
|
3976
|
+
) THEN
|
|
3977
|
+
DROP FUNCTION pgmq_public.pop(text);
|
|
3978
|
+
END IF;
|
|
3979
|
+
END
|
|
3980
|
+
$pgmq_receipt_types$;
|
|
3981
|
+
|
|
3982
|
+
CREATE OR REPLACE FUNCTION pgmq_public.require_seconds(value integer)
|
|
3983
|
+
RETURNS integer LANGUAGE plpgsql IMMUTABLE SET search_path = ''
|
|
3984
|
+
AS $$
|
|
3985
|
+
BEGIN
|
|
3986
|
+
IF value IS NULL OR value < 0 THEN
|
|
3987
|
+
RAISE EXCEPTION 'SUPACLOUD_QUEUE_SECONDS_INVALID' USING ERRCODE = '22023';
|
|
3988
|
+
END IF;
|
|
3989
|
+
RETURN value;
|
|
3990
|
+
END;
|
|
3991
|
+
$$;
|
|
3992
|
+
|
|
3993
|
+
CREATE OR REPLACE FUNCTION pgmq_public.require_read_count(value integer)
|
|
3994
|
+
RETURNS integer LANGUAGE plpgsql IMMUTABLE SET search_path = ''
|
|
3995
|
+
AS $$
|
|
3996
|
+
BEGIN
|
|
3997
|
+
IF value IS NULL OR value < 1 OR value > 10000 THEN
|
|
3998
|
+
RAISE EXCEPTION 'SUPACLOUD_QUEUE_COUNT_INVALID' USING ERRCODE = '22023';
|
|
3999
|
+
END IF;
|
|
4000
|
+
RETURN value;
|
|
4001
|
+
END;
|
|
4002
|
+
$$;
|
|
4003
|
+
|
|
4004
|
+
CREATE OR REPLACE FUNCTION pgmq_public.require_message_id(value bigint)
|
|
4005
|
+
RETURNS bigint LANGUAGE plpgsql IMMUTABLE SET search_path = ''
|
|
4006
|
+
AS $$
|
|
4007
|
+
BEGIN
|
|
4008
|
+
IF value IS NULL OR value < 1 THEN
|
|
4009
|
+
RAISE EXCEPTION 'SUPACLOUD_QUEUE_MESSAGE_ID_INVALID' USING ERRCODE = '22023';
|
|
4010
|
+
END IF;
|
|
4011
|
+
RETURN value;
|
|
4012
|
+
END;
|
|
4013
|
+
$$;
|
|
4014
|
+
|
|
4015
|
+
CREATE OR REPLACE FUNCTION pgmq_public.message_node_count(value jsonb)
|
|
4016
|
+
RETURNS integer LANGUAGE plpgsql IMMUTABLE SET search_path = ''
|
|
4017
|
+
AS $$
|
|
4018
|
+
DECLARE
|
|
4019
|
+
node_count integer;
|
|
4020
|
+
maximum_depth integer;
|
|
4021
|
+
BEGIN
|
|
4022
|
+
IF value IS NULL THEN
|
|
4023
|
+
RAISE EXCEPTION 'SUPACLOUD_QUEUE_MESSAGE_INVALID' USING ERRCODE = '22023';
|
|
4024
|
+
END IF;
|
|
4025
|
+
IF octet_length(convert_to(value::text, 'UTF8')) > 1048576 THEN
|
|
4026
|
+
RAISE EXCEPTION 'SUPACLOUD_QUEUE_PAYLOAD_TOO_LARGE' USING ERRCODE = '22023';
|
|
4027
|
+
END IF;
|
|
4028
|
+
WITH RECURSIVE tree(item, depth) AS (
|
|
4029
|
+
SELECT value, 0
|
|
4030
|
+
UNION ALL
|
|
4031
|
+
SELECT child.item, tree.depth + 1
|
|
4032
|
+
FROM tree
|
|
4033
|
+
CROSS JOIN LATERAL (
|
|
4034
|
+
SELECT element AS item
|
|
4035
|
+
FROM jsonb_array_elements(CASE WHEN jsonb_typeof(tree.item) = 'array' THEN tree.item ELSE '[]'::jsonb END) AS element
|
|
4036
|
+
UNION ALL
|
|
4037
|
+
SELECT entry.value AS item
|
|
4038
|
+
FROM jsonb_each(CASE WHEN jsonb_typeof(tree.item) = 'object' THEN tree.item ELSE '{}'::jsonb END) AS entry
|
|
4039
|
+
) AS child
|
|
4040
|
+
WHERE tree.depth < 65
|
|
4041
|
+
)
|
|
4042
|
+
SELECT count(*)::integer, max(depth) INTO node_count, maximum_depth
|
|
4043
|
+
FROM (SELECT depth FROM tree LIMIT 10001) AS bounded;
|
|
4044
|
+
IF node_count > 10000 OR maximum_depth > 64 THEN
|
|
4045
|
+
RAISE EXCEPTION 'SUPACLOUD_QUEUE_STRUCTURE_TOO_LARGE' USING ERRCODE = '22023';
|
|
4046
|
+
END IF;
|
|
4047
|
+
RETURN node_count;
|
|
4048
|
+
END;
|
|
4049
|
+
$$;
|
|
4050
|
+
|
|
4051
|
+
CREATE OR REPLACE FUNCTION pgmq_public.require_message(value jsonb)
|
|
4052
|
+
RETURNS jsonb LANGUAGE plpgsql IMMUTABLE SET search_path = ''
|
|
4053
|
+
AS $$
|
|
4054
|
+
BEGIN
|
|
4055
|
+
PERFORM pgmq_public.message_node_count(value);
|
|
4056
|
+
RETURN value;
|
|
4057
|
+
END;
|
|
4058
|
+
$$;
|
|
4059
|
+
|
|
4060
|
+
CREATE OR REPLACE FUNCTION pgmq_public.require_messages(values_ jsonb[])
|
|
4061
|
+
RETURNS jsonb[] LANGUAGE plpgsql IMMUTABLE SET search_path = ''
|
|
4062
|
+
AS $$
|
|
4063
|
+
DECLARE
|
|
4064
|
+
item jsonb;
|
|
4065
|
+
total_bytes bigint := 0;
|
|
4066
|
+
total_nodes integer := 0;
|
|
4067
|
+
BEGIN
|
|
4068
|
+
IF values_ IS NULL OR cardinality(values_) < 1 OR cardinality(values_) > 10000
|
|
4069
|
+
OR array_ndims(values_) <> 1 OR array_lower(values_, 1) <> 1 THEN
|
|
4070
|
+
RAISE EXCEPTION 'SUPACLOUD_QUEUE_BATCH_INVALID' USING ERRCODE = '22023';
|
|
4071
|
+
END IF;
|
|
4072
|
+
FOREACH item IN ARRAY values_ LOOP
|
|
4073
|
+
total_nodes := total_nodes + pgmq_public.message_node_count(item);
|
|
4074
|
+
IF total_nodes > 100000 THEN
|
|
4075
|
+
RAISE EXCEPTION 'SUPACLOUD_QUEUE_STRUCTURE_TOO_LARGE' USING ERRCODE = '22023';
|
|
4076
|
+
END IF;
|
|
4077
|
+
total_bytes := total_bytes + octet_length(convert_to(item::text, 'UTF8'));
|
|
4078
|
+
IF total_bytes > 8388608 THEN
|
|
4079
|
+
RAISE EXCEPTION 'SUPACLOUD_QUEUE_PAYLOAD_TOO_LARGE' USING ERRCODE = '22023';
|
|
4080
|
+
END IF;
|
|
4081
|
+
END LOOP;
|
|
4082
|
+
RETURN values_;
|
|
3939
4083
|
END;
|
|
3940
4084
|
$$;
|
|
3941
4085
|
|
|
3942
4086
|
CREATE OR REPLACE FUNCTION pgmq_public.send(queue_name text, message jsonb, sleep_seconds integer DEFAULT 0)
|
|
3943
|
-
RETURNS SETOF
|
|
4087
|
+
RETURNS SETOF text
|
|
3944
4088
|
LANGUAGE sql
|
|
3945
4089
|
VOLATILE
|
|
3946
4090
|
SECURITY DEFINER
|
|
3947
4091
|
SET search_path = ''
|
|
3948
|
-
AS $$ SELECT
|
|
4092
|
+
AS $$ SELECT receipt.msg_id::text FROM pgmq.send(pgmq_public.require_public_queue(queue_name), pgmq_public.require_message(message), pgmq_public.require_seconds(sleep_seconds)) AS receipt(msg_id); $$;
|
|
3949
4093
|
|
|
3950
4094
|
CREATE OR REPLACE FUNCTION pgmq_public.send_batch(queue_name text, messages jsonb[], sleep_seconds integer DEFAULT 0)
|
|
3951
|
-
RETURNS SETOF
|
|
4095
|
+
RETURNS SETOF text
|
|
3952
4096
|
LANGUAGE sql
|
|
3953
4097
|
VOLATILE
|
|
3954
4098
|
SECURITY DEFINER
|
|
3955
4099
|
SET search_path = ''
|
|
3956
|
-
AS $$ SELECT
|
|
4100
|
+
AS $$ SELECT receipt.msg_id::text FROM pgmq.send_batch(pgmq_public.require_public_queue(queue_name), pgmq_public.require_messages(messages), pgmq_public.require_seconds(sleep_seconds)) AS receipt(msg_id); $$;
|
|
3957
4101
|
|
|
3958
4102
|
CREATE OR REPLACE FUNCTION pgmq_public.read(queue_name text, sleep_seconds integer, n integer)
|
|
3959
|
-
RETURNS
|
|
4103
|
+
RETURNS TABLE (
|
|
4104
|
+
msg_id text, read_ct integer, enqueued_at timestamptz,
|
|
4105
|
+
last_read_at timestamptz, vt timestamptz, message jsonb, headers jsonb
|
|
4106
|
+
)
|
|
3960
4107
|
LANGUAGE sql
|
|
3961
4108
|
VOLATILE
|
|
3962
4109
|
SECURITY DEFINER
|
|
3963
4110
|
SET search_path = ''
|
|
3964
|
-
AS $$
|
|
4111
|
+
AS $$
|
|
4112
|
+
SELECT receipt.msg_id::text, receipt.read_ct, receipt.enqueued_at,
|
|
4113
|
+
(to_jsonb(receipt)->>'last_read_at')::timestamptz, receipt.vt,
|
|
4114
|
+
receipt.message, to_jsonb(receipt)->'headers'
|
|
4115
|
+
FROM pgmq.read(pgmq_public.require_public_queue(queue_name),
|
|
4116
|
+
pgmq_public.require_seconds(sleep_seconds), pgmq_public.require_read_count(n)) AS receipt;
|
|
4117
|
+
$$;
|
|
3965
4118
|
|
|
3966
4119
|
CREATE OR REPLACE FUNCTION pgmq_public.pop(queue_name text)
|
|
3967
|
-
RETURNS
|
|
4120
|
+
RETURNS TABLE (
|
|
4121
|
+
msg_id text, read_ct integer, enqueued_at timestamptz,
|
|
4122
|
+
last_read_at timestamptz, vt timestamptz, message jsonb, headers jsonb
|
|
4123
|
+
)
|
|
3968
4124
|
LANGUAGE sql
|
|
3969
4125
|
VOLATILE
|
|
3970
4126
|
SECURITY DEFINER
|
|
3971
4127
|
SET search_path = ''
|
|
3972
|
-
AS $$
|
|
4128
|
+
AS $$
|
|
4129
|
+
SELECT receipt.msg_id::text, receipt.read_ct, receipt.enqueued_at,
|
|
4130
|
+
(to_jsonb(receipt)->>'last_read_at')::timestamptz, receipt.vt,
|
|
4131
|
+
receipt.message, to_jsonb(receipt)->'headers'
|
|
4132
|
+
FROM pgmq.pop(pgmq_public.require_public_queue(queue_name)) AS receipt;
|
|
4133
|
+
$$;
|
|
3973
4134
|
|
|
3974
4135
|
CREATE OR REPLACE FUNCTION pgmq_public.archive(queue_name text, message_id bigint)
|
|
3975
4136
|
RETURNS boolean
|
|
@@ -3977,7 +4138,7 @@ LANGUAGE sql
|
|
|
3977
4138
|
VOLATILE
|
|
3978
4139
|
SECURITY DEFINER
|
|
3979
4140
|
SET search_path = ''
|
|
3980
|
-
AS $$ SELECT pgmq.archive(pgmq_public.require_public_queue(queue_name), message_id); $$;
|
|
4141
|
+
AS $$ SELECT pgmq.archive(pgmq_public.require_public_queue(queue_name), pgmq_public.require_message_id(message_id)); $$;
|
|
3981
4142
|
|
|
3982
4143
|
CREATE OR REPLACE FUNCTION pgmq_public."delete"(queue_name text, message_id bigint)
|
|
3983
4144
|
RETURNS boolean
|
|
@@ -3985,10 +4146,12 @@ LANGUAGE sql
|
|
|
3985
4146
|
VOLATILE
|
|
3986
4147
|
SECURITY DEFINER
|
|
3987
4148
|
SET search_path = ''
|
|
3988
|
-
AS $$ SELECT pgmq.delete(pgmq_public.require_public_queue(queue_name), message_id); $$;
|
|
4149
|
+
AS $$ SELECT pgmq.delete(pgmq_public.require_public_queue(queue_name), pgmq_public.require_message_id(message_id)); $$;
|
|
3989
4150
|
|
|
3990
4151
|
REVOKE ALL ON ALL FUNCTIONS IN SCHEMA pgmq_public FROM PUBLIC;
|
|
3991
4152
|
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA pgmq_public TO anon, authenticated, service_role;
|
|
4153
|
+
|
|
4154
|
+
NOTIFY pgrst, 'reload schema';
|
|
3992
4155
|
-- supacloud:sql-module:pgmq-public:end
|
|
3993
4156
|
`;
|
|
3994
4157
|
var WORKFLOWS_SQL = `
|
|
@@ -4611,7 +4774,12 @@ BEGIN
|
|
|
4611
4774
|
OR (retry_receipt ->> 'delaySeconds')::integer IS DISTINCT FROM p_delay_seconds THEN
|
|
4612
4775
|
RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_IDEMPOTENCY_CONFLICT' USING ERRCODE = '23505';
|
|
4613
4776
|
END IF;
|
|
4614
|
-
|
|
4777
|
+
retry_receipt := retry_receipt || jsonb_build_object(
|
|
4778
|
+
'stepId', current_step.id,
|
|
4779
|
+
'attempt', p_attempt
|
|
4780
|
+
);
|
|
4781
|
+
RETURN supacloud_workflows.snapshot(current_step.run_id, true)
|
|
4782
|
+
|| jsonb_build_object('retryReceipt', retry_receipt);
|
|
4615
4783
|
END IF;
|
|
4616
4784
|
IF normalized_worker_id IS NULL
|
|
4617
4785
|
OR p_message_id IS NULL
|
|
@@ -4639,19 +4807,22 @@ BEGIN
|
|
|
4639
4807
|
IF NOT FOUND THEN
|
|
4640
4808
|
RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_RUN_NOT_ACTIVE' USING ERRCODE = '55000';
|
|
4641
4809
|
END IF;
|
|
4810
|
+
retry_receipt := jsonb_build_object(
|
|
4811
|
+
'operation', 'retry',
|
|
4812
|
+
'stepId', current_step.id,
|
|
4813
|
+
'messageId', p_message_id::text,
|
|
4814
|
+
'attempt', p_attempt,
|
|
4815
|
+
'workerId', normalized_worker_id,
|
|
4816
|
+
'errorMessage', normalized_error,
|
|
4817
|
+
'delaySeconds', p_delay_seconds
|
|
4818
|
+
);
|
|
4642
4819
|
INSERT INTO supacloud_workflows.events (
|
|
4643
4820
|
run_id, step_id, event_type, attempt, details
|
|
4644
4821
|
) VALUES (
|
|
4645
|
-
current_step.run_id, current_step.id, 'step_dead_lettered', p_attempt,
|
|
4646
|
-
jsonb_build_object(
|
|
4647
|
-
'operation', 'retry',
|
|
4648
|
-
'messageId', p_message_id::text,
|
|
4649
|
-
'workerId', normalized_worker_id,
|
|
4650
|
-
'errorMessage', normalized_error,
|
|
4651
|
-
'delaySeconds', p_delay_seconds
|
|
4652
|
-
)
|
|
4822
|
+
current_step.run_id, current_step.id, 'step_dead_lettered', p_attempt, retry_receipt
|
|
4653
4823
|
);
|
|
4654
|
-
RETURN supacloud_workflows.snapshot(current_step.run_id, false)
|
|
4824
|
+
RETURN supacloud_workflows.snapshot(current_step.run_id, false)
|
|
4825
|
+
|| jsonb_build_object('retryReceipt', retry_receipt);
|
|
4655
4826
|
END IF;
|
|
4656
4827
|
|
|
4657
4828
|
SELECT EXISTS (
|
|
@@ -4664,19 +4835,22 @@ BEGIN
|
|
|
4664
4835
|
SET status = 'queued', error_message = normalized_error,
|
|
4665
4836
|
retry_delay_seconds = p_delay_seconds, updated_at = now()
|
|
4666
4837
|
WHERE id = current_step.id;
|
|
4838
|
+
retry_receipt := jsonb_build_object(
|
|
4839
|
+
'operation', 'retry',
|
|
4840
|
+
'stepId', current_step.id,
|
|
4841
|
+
'messageId', p_message_id::text,
|
|
4842
|
+
'attempt', p_attempt,
|
|
4843
|
+
'workerId', normalized_worker_id,
|
|
4844
|
+
'errorMessage', normalized_error,
|
|
4845
|
+
'delaySeconds', p_delay_seconds
|
|
4846
|
+
);
|
|
4667
4847
|
INSERT INTO supacloud_workflows.events (
|
|
4668
4848
|
run_id, step_id, event_type, attempt, details
|
|
4669
4849
|
) VALUES (
|
|
4670
|
-
current_step.run_id, current_step.id, 'step_retried', p_attempt,
|
|
4671
|
-
jsonb_build_object(
|
|
4672
|
-
'operation', 'retry',
|
|
4673
|
-
'messageId', p_message_id::text,
|
|
4674
|
-
'workerId', normalized_worker_id,
|
|
4675
|
-
'errorMessage', normalized_error,
|
|
4676
|
-
'delaySeconds', p_delay_seconds
|
|
4677
|
-
)
|
|
4850
|
+
current_step.run_id, current_step.id, 'step_retried', p_attempt, retry_receipt
|
|
4678
4851
|
);
|
|
4679
|
-
RETURN supacloud_workflows.snapshot(current_step.run_id, false)
|
|
4852
|
+
RETURN supacloud_workflows.snapshot(current_step.run_id, false)
|
|
4853
|
+
|| jsonb_build_object('retryReceipt', retry_receipt);
|
|
4680
4854
|
END;
|
|
4681
4855
|
$$;
|
|
4682
4856
|
|
|
@@ -5087,6 +5261,8 @@ CREATE TABLE IF NOT EXISTS supacloud_commands.receipts (
|
|
|
5087
5261
|
CREATE INDEX IF NOT EXISTS supacloud_commands_target_idx
|
|
5088
5262
|
ON supacloud_commands.receipts (target_type, target_id, created_at DESC, id);
|
|
5089
5263
|
|
|
5264
|
+
ALTER TABLE supacloud_commands.receipts ADD COLUMN IF NOT EXISTS tenant_id text;
|
|
5265
|
+
|
|
5090
5266
|
CREATE OR REPLACE FUNCTION supacloud_commands.snapshot(
|
|
5091
5267
|
p_command_id uuid,
|
|
5092
5268
|
p_idempotent boolean DEFAULT false
|
|
@@ -5098,6 +5274,7 @@ LANGUAGE sql STABLE SECURITY DEFINER SET search_path = '' AS $$
|
|
|
5098
5274
|
'targetType', receipt.target_type,
|
|
5099
5275
|
'targetId', receipt.target_id,
|
|
5100
5276
|
'actorId', receipt.actor_id,
|
|
5277
|
+
'tenantId', receipt.tenant_id,
|
|
5101
5278
|
'payloadFingerprint', receipt.payload_fingerprint,
|
|
5102
5279
|
'createdAt', receipt.created_at,
|
|
5103
5280
|
'idempotent', p_idempotent,
|
|
@@ -5125,6 +5302,8 @@ DECLARE
|
|
|
5125
5302
|
normalized_target_id text;
|
|
5126
5303
|
fingerprint text;
|
|
5127
5304
|
existing supacloud_commands.receipts%ROWTYPE;
|
|
5305
|
+
tenant_id text;
|
|
5306
|
+
existing_execution jsonb;
|
|
5128
5307
|
BEGIN
|
|
5129
5308
|
IF jsonb_typeof(request) IS DISTINCT FROM 'object' THEN
|
|
5130
5309
|
RAISE EXCEPTION 'SUPACLOUD_COMMAND_INVALID' USING ERRCODE = '22023';
|
|
@@ -5135,6 +5314,11 @@ BEGIN
|
|
|
5135
5314
|
RAISE EXCEPTION 'SUPACLOUD_COMMAND_INVALID' USING ERRCODE = '22023';
|
|
5136
5315
|
END IF;
|
|
5137
5316
|
command_id := command_id_text::uuid;
|
|
5317
|
+
tenant_id := request->>'tenantId';
|
|
5318
|
+
IF request ? 'tenantId' AND (jsonb_typeof(request->'tenantId') IS DISTINCT FROM 'string'
|
|
5319
|
+
OR tenant_id !~ '^[A-Za-z0-9._:@|-]{1,200}$') THEN
|
|
5320
|
+
RAISE EXCEPTION 'SUPACLOUD_COMMAND_INVALID' USING ERRCODE = '22023';
|
|
5321
|
+
END IF;
|
|
5138
5322
|
IF request ? 'actorId' AND request ->> 'actorId' IS NOT NULL THEN
|
|
5139
5323
|
actor_id := (request ->> 'actorId')::uuid;
|
|
5140
5324
|
END IF;
|
|
@@ -5162,12 +5346,20 @@ BEGIN
|
|
|
5162
5346
|
OR existing.target_type <> normalized_target_type
|
|
5163
5347
|
OR existing.target_id <> normalized_target_id
|
|
5164
5348
|
OR existing.actor_id IS DISTINCT FROM actor_id
|
|
5349
|
+
OR existing.tenant_id IS DISTINCT FROM tenant_id
|
|
5165
5350
|
OR existing.payload <> payload
|
|
5166
5351
|
OR existing.payload_fingerprint <> fingerprint THEN
|
|
5167
5352
|
RAISE EXCEPTION 'SUPACLOUD_COMMAND_IDEMPOTENCY_CONFLICT' USING ERRCODE = '23505';
|
|
5168
5353
|
END IF;
|
|
5169
5354
|
RETURN supacloud_commands.snapshot(command_id, true);
|
|
5170
5355
|
END IF;
|
|
5356
|
+
IF to_regprocedure('supacloud_commands.execution_status(jsonb)') IS NOT NULL THEN
|
|
5357
|
+
EXECUTE 'SELECT supacloud_commands.execution_status($1)' INTO existing_execution
|
|
5358
|
+
USING jsonb_build_object('commandId',command_id);
|
|
5359
|
+
IF existing_execution IS NOT NULL THEN
|
|
5360
|
+
RAISE EXCEPTION 'SUPACLOUD_COMMAND_IDEMPOTENCY_CONFLICT' USING ERRCODE = '23505';
|
|
5361
|
+
END IF;
|
|
5362
|
+
END IF;
|
|
5171
5363
|
|
|
5172
5364
|
PERFORM supacloud_workflows.start_run(
|
|
5173
5365
|
command_id,
|
|
@@ -5188,10 +5380,10 @@ BEGIN
|
|
|
5188
5380
|
|
|
5189
5381
|
INSERT INTO supacloud_commands.receipts (
|
|
5190
5382
|
id, command_type, target_type, target_id, actor_id, payload,
|
|
5191
|
-
payload_fingerprint, workflow_run_id
|
|
5383
|
+
payload_fingerprint, workflow_run_id, tenant_id
|
|
5192
5384
|
) VALUES (
|
|
5193
5385
|
command_id, normalized_command_type, normalized_target_type,
|
|
5194
|
-
normalized_target_id, actor_id, payload, fingerprint, command_id
|
|
5386
|
+
normalized_target_id, actor_id, payload, fingerprint, command_id, tenant_id
|
|
5195
5387
|
);
|
|
5196
5388
|
RETURN supacloud_commands.snapshot(command_id, false);
|
|
5197
5389
|
EXCEPTION
|
|
@@ -5200,33 +5392,79 @@ EXCEPTION
|
|
|
5200
5392
|
END;
|
|
5201
5393
|
$$;
|
|
5202
5394
|
|
|
5203
|
-
CREATE OR REPLACE FUNCTION
|
|
5204
|
-
RETURNS jsonb
|
|
5205
|
-
LANGUAGE sql VOLATILE SECURITY DEFINER SET search_path = '' AS $$
|
|
5206
|
-
SELECT supacloud_commands.submit(request)
|
|
5207
|
-
$$;
|
|
5208
|
-
|
|
5209
|
-
CREATE OR REPLACE FUNCTION public.supacloud_command_get(request jsonb)
|
|
5395
|
+
CREATE OR REPLACE FUNCTION supacloud_commands.status(request jsonb)
|
|
5210
5396
|
RETURNS jsonb
|
|
5211
5397
|
LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = '' AS $$
|
|
5212
5398
|
DECLARE
|
|
5213
5399
|
command_id_text text;
|
|
5400
|
+
execution jsonb;
|
|
5401
|
+
submission jsonb;
|
|
5402
|
+
field text;
|
|
5214
5403
|
BEGIN
|
|
5215
5404
|
IF jsonb_typeof(request) IS DISTINCT FROM 'object' THEN
|
|
5216
5405
|
RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';
|
|
5217
5406
|
END IF;
|
|
5407
|
+
IF request ? 'commandId' THEN
|
|
5408
|
+
IF jsonb_typeof(request->'commandId') IS DISTINCT FROM 'string'
|
|
5409
|
+
OR request->>'commandId' !~* '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
|
|
5410
|
+
OR EXISTS(SELECT FROM jsonb_object_keys(request) AS k WHERE k <> 'commandId') THEN
|
|
5411
|
+
RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';
|
|
5412
|
+
END IF;
|
|
5413
|
+
request := jsonb_build_object('commandId',(request->>'commandId')::uuid::text);
|
|
5414
|
+
ELSE
|
|
5415
|
+
IF EXISTS(SELECT FROM jsonb_object_keys(request) AS k WHERE k NOT IN ('tenantId','actorId','command','operationId')) THEN
|
|
5416
|
+
RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';
|
|
5417
|
+
END IF;
|
|
5418
|
+
FOREACH field IN ARRAY ARRAY['tenantId','actorId','command','operationId'] LOOP
|
|
5419
|
+
IF jsonb_typeof(request->field) IS DISTINCT FROM 'string' OR request->>field !~ '^[A-Za-z0-9._:@|-]{1,200}$' THEN
|
|
5420
|
+
RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';
|
|
5421
|
+
END IF;
|
|
5422
|
+
END LOOP;
|
|
5423
|
+
END IF;
|
|
5424
|
+
IF to_regprocedure('supacloud_commands.execution_status(jsonb)') IS NOT NULL THEN
|
|
5425
|
+
EXECUTE 'SELECT supacloud_commands.execution_status($1)' INTO execution USING request;
|
|
5426
|
+
IF execution IS NOT NULL THEN RETURN execution; END IF;
|
|
5427
|
+
END IF;
|
|
5428
|
+
IF NOT (request ? 'commandId') THEN
|
|
5429
|
+
IF jsonb_typeof(request->'tenantId') IS DISTINCT FROM 'string'
|
|
5430
|
+
OR jsonb_typeof(request->'actorId') IS DISTINCT FROM 'string'
|
|
5431
|
+
OR jsonb_typeof(request->'command') IS DISTINCT FROM 'string'
|
|
5432
|
+
OR jsonb_typeof(request->'operationId') IS DISTINCT FROM 'string' THEN
|
|
5433
|
+
RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';
|
|
5434
|
+
END IF;
|
|
5435
|
+
RETURN NULL;
|
|
5436
|
+
END IF;
|
|
5218
5437
|
command_id_text := request ->> 'commandId';
|
|
5219
5438
|
IF command_id_text IS NULL
|
|
5220
5439
|
OR command_id_text !~* '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' THEN
|
|
5221
5440
|
RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';
|
|
5222
5441
|
END IF;
|
|
5223
|
-
|
|
5442
|
+
submission := supacloud_commands.snapshot(command_id_text::uuid, false);
|
|
5443
|
+
IF submission IS NULL THEN RETURN NULL; END IF;
|
|
5444
|
+
RETURN jsonb_build_object('kind','submission','commandId',command_id_text::uuid,
|
|
5445
|
+
'execution',NULL,'workflow',jsonb_build_object(
|
|
5446
|
+
'runId',submission->'workflow'->'runId','status',submission->'workflow'->'status'));
|
|
5224
5447
|
EXCEPTION
|
|
5225
5448
|
WHEN invalid_parameter_value OR invalid_text_representation THEN
|
|
5226
5449
|
RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';
|
|
5227
5450
|
END;
|
|
5228
5451
|
$$;
|
|
5229
5452
|
|
|
5453
|
+
CREATE OR REPLACE FUNCTION public.supacloud_command_submit(request jsonb)
|
|
5454
|
+
RETURNS jsonb
|
|
5455
|
+
LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path = '' AS $$
|
|
5456
|
+
BEGIN
|
|
5457
|
+
PERFORM supacloud_commands.submit(request);
|
|
5458
|
+
RETURN supacloud_commands.status(jsonb_build_object('commandId',request->>'commandId'));
|
|
5459
|
+
END
|
|
5460
|
+
$$;
|
|
5461
|
+
|
|
5462
|
+
CREATE OR REPLACE FUNCTION public.supacloud_command_get(request jsonb)
|
|
5463
|
+
RETURNS jsonb
|
|
5464
|
+
LANGUAGE sql STABLE SECURITY DEFINER SET search_path = '' AS $$
|
|
5465
|
+
SELECT supacloud_commands.status(request)
|
|
5466
|
+
$$;
|
|
5467
|
+
|
|
5230
5468
|
REVOKE ALL ON ALL TABLES IN SCHEMA supacloud_commands
|
|
5231
5469
|
FROM PUBLIC, anon, authenticated, service_role;
|
|
5232
5470
|
REVOKE ALL ON ALL FUNCTIONS IN SCHEMA supacloud_commands
|
|
@@ -6894,9 +7132,9 @@ class RealtimeEngine {
|
|
|
6894
7132
|
return;
|
|
6895
7133
|
const [, joinRefLen, refLen, topicLen, eventLen, metaLen, encoding] = bytes;
|
|
6896
7134
|
let offset = 7;
|
|
6897
|
-
const
|
|
7135
|
+
const decoder = new TextDecoder;
|
|
6898
7136
|
const read = (len) => {
|
|
6899
|
-
const out =
|
|
7137
|
+
const out = decoder.decode(bytes.subarray(offset, offset + len));
|
|
6900
7138
|
offset += len;
|
|
6901
7139
|
return out;
|
|
6902
7140
|
};
|
|
@@ -6909,7 +7147,7 @@ class RealtimeEngine {
|
|
|
6909
7147
|
const payload = {
|
|
6910
7148
|
type: "broadcast",
|
|
6911
7149
|
event: userEvent,
|
|
6912
|
-
payload: encoding === 1 ? safeJsonParse(
|
|
7150
|
+
payload: encoding === 1 ? safeJsonParse(decoder.decode(payloadBytes)) : payloadBytes
|
|
6913
7151
|
};
|
|
6914
7152
|
this.handleBroadcast(conn, {
|
|
6915
7153
|
topic,
|
|
@@ -7220,24 +7458,24 @@ function parseQuery(searchParams) {
|
|
|
7220
7458
|
const segments = key.split(".");
|
|
7221
7459
|
const last = segments[segments.length - 1];
|
|
7222
7460
|
if (last === "order") {
|
|
7223
|
-
const
|
|
7461
|
+
const path = segments.slice(0, -1);
|
|
7224
7462
|
for (const term of splitTopLevel(value, ",")) {
|
|
7225
|
-
q.order.push(parseOrderTerm(term.trim(),
|
|
7463
|
+
q.order.push(parseOrderTerm(term.trim(), path));
|
|
7226
7464
|
}
|
|
7227
7465
|
continue;
|
|
7228
7466
|
}
|
|
7229
7467
|
if (last === "limit" || last === "offset") {
|
|
7230
|
-
const
|
|
7468
|
+
const path = segments.slice(0, -1).join(".");
|
|
7231
7469
|
const n = parseInt(value, 10);
|
|
7232
7470
|
if (Number.isNaN(n) || n < 0)
|
|
7233
7471
|
throw new ParseError(`invalid ${last}: ${value}`);
|
|
7234
|
-
(last === "limit" ? q.limits : q.offsets).set(
|
|
7472
|
+
(last === "limit" ? q.limits : q.offsets).set(path, n);
|
|
7235
7473
|
continue;
|
|
7236
7474
|
}
|
|
7237
7475
|
if (last === "or" || last === "and") {
|
|
7238
7476
|
const negated = segments[segments.length - 2] === "not";
|
|
7239
|
-
const
|
|
7240
|
-
q.conditions.push(parseLogicTree(last, value,
|
|
7477
|
+
const path = segments.slice(0, negated ? -2 : -1);
|
|
7478
|
+
q.conditions.push(parseLogicTree(last, value, path, negated));
|
|
7241
7479
|
continue;
|
|
7242
7480
|
}
|
|
7243
7481
|
if (RESERVED.has(key))
|
|
@@ -7677,13 +7915,13 @@ class QueryBuilder {
|
|
|
7677
7915
|
if (c.kind !== "column" || c.name === "*") {
|
|
7678
7916
|
throw new ParseError("spread embeds support explicit columns only");
|
|
7679
7917
|
}
|
|
7680
|
-
const
|
|
7918
|
+
const expr = renderColumnExpr(childAlias, c.name);
|
|
7681
7919
|
const cast = c.cast ? `::${sanitizeCast(c.cast)}` : "";
|
|
7682
7920
|
const out = c.alias ?? defaultAliasFor(c.name);
|
|
7683
|
-
return toOne ? `(select ${
|
|
7921
|
+
return toOne ? `(select ${expr}${cast} from ${fromClause}${where}) as ${quoteIdent(out)}` : `coalesce((select json_agg(${expr}${cast}${order}) from ${fromClause}${where}), ${AGG_EMPTY}) as ${quoteIdent(out)}`;
|
|
7684
7922
|
});
|
|
7685
|
-
const
|
|
7686
|
-
return { exprs, innerCond
|
|
7923
|
+
const innerCond = embed.inner ? `exists (select 1 from ${fromClause}${where})` : undefined;
|
|
7924
|
+
return { exprs, innerCond };
|
|
7687
7925
|
}
|
|
7688
7926
|
const sub = `select ${childExprs.join(", ")} from ${fromClause}${where}${order}${limitOffset}`;
|
|
7689
7927
|
const expr = rel.single === true ? `(select row_to_json(_sub) from (${sub}) _sub) as ${quoteIdent(outName)}` : `coalesce((select json_agg(row_to_json(_sub)) from (${sub}) _sub), ${AGG_EMPTY}) as ${quoteIdent(outName)}`;
|
|
@@ -8142,12 +8380,12 @@ class RestHandler {
|
|
|
8142
8380
|
const built = builder.buildSelect(table, { count: prefer.count !== undefined });
|
|
8143
8381
|
const { rows, count } = await this.db.withContext(ctx, async (query) => {
|
|
8144
8382
|
const res = await query(built.sql, built.params);
|
|
8145
|
-
let
|
|
8383
|
+
let count = null;
|
|
8146
8384
|
if (built.countSql) {
|
|
8147
8385
|
const c = await query(built.countSql, []);
|
|
8148
|
-
|
|
8386
|
+
count = c.rows[0].count;
|
|
8149
8387
|
}
|
|
8150
|
-
return { rows: res.rows[0].body, count
|
|
8388
|
+
return { rows: res.rows[0].body, count };
|
|
8151
8389
|
});
|
|
8152
8390
|
const offset = q.offsets.get("") ?? 0;
|
|
8153
8391
|
return this.dataResponse(rows, {
|
|
@@ -8343,12 +8581,12 @@ class RestHandler {
|
|
|
8343
8581
|
const countSql = prefer.count !== undefined ? `select count(*)::int as count from ${call} as ${quoteIdent(alias)}${where}` : undefined;
|
|
8344
8582
|
const { rows, count } = await this.db.withContext(ctx, async (query) => {
|
|
8345
8583
|
const res = await query(sql, []);
|
|
8346
|
-
let
|
|
8584
|
+
let count = null;
|
|
8347
8585
|
if (countSql) {
|
|
8348
8586
|
const c = await query(countSql, []);
|
|
8349
|
-
|
|
8587
|
+
count = c.rows[0].count;
|
|
8350
8588
|
}
|
|
8351
|
-
return { rows: res.rows[0].body, count
|
|
8589
|
+
return { rows: res.rows[0].body, count };
|
|
8352
8590
|
});
|
|
8353
8591
|
return this.dataResponse(rows, {
|
|
8354
8592
|
status: 200,
|
|
@@ -8664,7 +8902,7 @@ class Semaphore {
|
|
|
8664
8902
|
this.active += 1;
|
|
8665
8903
|
return;
|
|
8666
8904
|
}
|
|
8667
|
-
await new Promise((
|
|
8905
|
+
await new Promise((resolve) => this.waiters.push(resolve));
|
|
8668
8906
|
}
|
|
8669
8907
|
release() {
|
|
8670
8908
|
const next = this.waiters.shift();
|
|
@@ -8870,22 +9108,22 @@ class StorageHandler {
|
|
|
8870
9108
|
}
|
|
8871
9109
|
if (parts[0] === "render" && parts[1] === "image" && parts.length >= 5) {
|
|
8872
9110
|
const kind = parts[2];
|
|
8873
|
-
const
|
|
8874
|
-
const
|
|
9111
|
+
const bucket = parts[3];
|
|
9112
|
+
const key = parts.slice(4).join("/");
|
|
8875
9113
|
if (kind === "public" && (method === "GET" || method === "HEAD")) {
|
|
8876
|
-
const source = await this.loadPublicObject(
|
|
9114
|
+
const source = await this.loadPublicObject(bucket, key);
|
|
8877
9115
|
if (source instanceof Response)
|
|
8878
9116
|
return source;
|
|
8879
9117
|
return await this.transformImageResponse(source, url, method === "HEAD");
|
|
8880
9118
|
}
|
|
8881
9119
|
if (kind === "authenticated" && (method === "GET" || method === "HEAD")) {
|
|
8882
|
-
const source = await this.loadAuthenticatedObject(ctx,
|
|
9120
|
+
const source = await this.loadAuthenticatedObject(ctx, bucket, key);
|
|
8883
9121
|
if (source instanceof Response)
|
|
8884
9122
|
return source;
|
|
8885
9123
|
return await this.transformImageResponse(source, url, method === "HEAD");
|
|
8886
9124
|
}
|
|
8887
9125
|
if (kind === "sign" && method === "GET") {
|
|
8888
|
-
const source = await this.loadSignedObject(url,
|
|
9126
|
+
const source = await this.loadSignedObject(url, bucket, key);
|
|
8889
9127
|
if (source instanceof Response)
|
|
8890
9128
|
return source;
|
|
8891
9129
|
return await this.transformImageResponse(source, url, false);
|
|
@@ -8919,12 +9157,12 @@ class StorageHandler {
|
|
|
8919
9157
|
return await this.redeemSignedUrl(url, parts[2], parts.slice(3).join("/"));
|
|
8920
9158
|
}
|
|
8921
9159
|
if (parts[1] === "upload" && parts[2] === "sign" && parts.length >= 5) {
|
|
8922
|
-
const
|
|
8923
|
-
const
|
|
9160
|
+
const bucket = parts[3];
|
|
9161
|
+
const key = parts.slice(4).join("/");
|
|
8924
9162
|
if (method === "POST")
|
|
8925
|
-
return await this.signUploadUrl(ctx,
|
|
9163
|
+
return await this.signUploadUrl(ctx, bucket, key);
|
|
8926
9164
|
if (method === "PUT")
|
|
8927
|
-
return await this.redeemSignedUpload(req, url,
|
|
9165
|
+
return await this.redeemSignedUpload(req, url, bucket, key);
|
|
8928
9166
|
}
|
|
8929
9167
|
if (parts[1] === "info" && parts.length >= 4 && method === "GET") {
|
|
8930
9168
|
return await this.objectInfo(ctx, parts[2], parts.slice(3).join("/"));
|
|
@@ -8959,8 +9197,8 @@ class StorageHandler {
|
|
|
8959
9197
|
async withMutation(operation) {
|
|
8960
9198
|
const previous = this.mutationTail;
|
|
8961
9199
|
let release;
|
|
8962
|
-
this.mutationTail = new Promise((
|
|
8963
|
-
release =
|
|
9200
|
+
this.mutationTail = new Promise((resolve) => {
|
|
9201
|
+
release = resolve;
|
|
8964
9202
|
});
|
|
8965
9203
|
await previous;
|
|
8966
9204
|
try {
|
|
@@ -9212,8 +9450,8 @@ class StorageHandler {
|
|
|
9212
9450
|
return storageError(409, "Duplicate", "The resource already exists");
|
|
9213
9451
|
throw error;
|
|
9214
9452
|
}
|
|
9215
|
-
const
|
|
9216
|
-
this.tusUploads.set(
|
|
9453
|
+
const uploadId = crypto.randomUUID();
|
|
9454
|
+
this.tusUploads.set(uploadId, {
|
|
9217
9455
|
bucketId,
|
|
9218
9456
|
key,
|
|
9219
9457
|
contentType,
|
|
@@ -9229,13 +9467,13 @@ class StorageHandler {
|
|
|
9229
9467
|
return new Response(null, {
|
|
9230
9468
|
status: 201,
|
|
9231
9469
|
headers: tus({
|
|
9232
|
-
location: `${url.origin}/storage/v1/upload/resumable/${
|
|
9470
|
+
location: `${url.origin}/storage/v1/upload/resumable/${uploadId}`,
|
|
9233
9471
|
"upload-offset": "0",
|
|
9234
9472
|
"upload-length": "0"
|
|
9235
9473
|
})
|
|
9236
9474
|
});
|
|
9237
9475
|
}
|
|
9238
|
-
const activeUploads = [...this.tusUploads.values()].filter((
|
|
9476
|
+
const activeUploads = [...this.tusUploads.values()].filter((upload) => upload.completedAt === undefined).length;
|
|
9239
9477
|
if (activeUploads >= MAX_CONCURRENT_TUS_UPLOADS) {
|
|
9240
9478
|
return storageError(429, "too_many_requests", "Too many resumable uploads are active");
|
|
9241
9479
|
}
|
|
@@ -9243,7 +9481,7 @@ class StorageHandler {
|
|
|
9243
9481
|
if (preflightError)
|
|
9244
9482
|
return preflightError;
|
|
9245
9483
|
const uploadId = crypto.randomUUID();
|
|
9246
|
-
const
|
|
9484
|
+
const upload = {
|
|
9247
9485
|
bucketId,
|
|
9248
9486
|
key,
|
|
9249
9487
|
contentType,
|
|
@@ -9254,7 +9492,7 @@ class StorageHandler {
|
|
|
9254
9492
|
chunks: [],
|
|
9255
9493
|
ctx
|
|
9256
9494
|
};
|
|
9257
|
-
this.tusUploads.set(uploadId,
|
|
9495
|
+
this.tusUploads.set(uploadId, upload);
|
|
9258
9496
|
if ((req.headers.get("content-type") ?? "").includes("application/offset+octet-stream")) {
|
|
9259
9497
|
const chunk = await readLimitedBody(req, length);
|
|
9260
9498
|
if (chunk === null) {
|
|
@@ -9262,7 +9500,7 @@ class StorageHandler {
|
|
|
9262
9500
|
return storageError(400, "invalid_request", "chunk exceeds the declared Upload-Length");
|
|
9263
9501
|
}
|
|
9264
9502
|
if (chunk.length > 0) {
|
|
9265
|
-
const err = await this.appendTus(uploadId,
|
|
9503
|
+
const err = await this.appendTus(uploadId, upload, 0, chunk);
|
|
9266
9504
|
if (err)
|
|
9267
9505
|
return err;
|
|
9268
9506
|
}
|
|
@@ -9271,7 +9509,7 @@ class StorageHandler {
|
|
|
9271
9509
|
status: 201,
|
|
9272
9510
|
headers: tus({
|
|
9273
9511
|
location: `${url.origin}/storage/v1/upload/resumable/${uploadId}`,
|
|
9274
|
-
"upload-offset": String(
|
|
9512
|
+
"upload-offset": String(upload.offset)
|
|
9275
9513
|
})
|
|
9276
9514
|
});
|
|
9277
9515
|
}
|
|
@@ -11197,8 +11435,8 @@ ${msg.text}` : `[mail] to=${msg.to} subject="${msg.subject}"`));
|
|
|
11197
11435
|
headers: { "content-type": "application/json" }
|
|
11198
11436
|
}));
|
|
11199
11437
|
}
|
|
11200
|
-
const
|
|
11201
|
-
return withCors(await auth.handle(req,
|
|
11438
|
+
const ctx = { role: String(keyClaims.role ?? "anon"), claims: keyClaims };
|
|
11439
|
+
return withCors(await auth.handle(req, ctx, url));
|
|
11202
11440
|
}
|
|
11203
11441
|
if (path.startsWith("/functions/v1/")) {
|
|
11204
11442
|
const functionName = path.replace(/^\/functions\/v1\/?/, "").split("/")[0];
|
|
@@ -11407,9 +11645,9 @@ function validatePublicationTables(tables) {
|
|
|
11407
11645
|
if (missing.length > 0) {
|
|
11408
11646
|
throw new Error(`PowerSync publication tables do not exist: ${missing.join(", ")}`);
|
|
11409
11647
|
}
|
|
11410
|
-
const
|
|
11411
|
-
if (
|
|
11412
|
-
throw new Error(`PowerSync publication only accepts ordinary or partitioned tables: ${
|
|
11648
|
+
const unsupported = tables.filter((table) => table.present && !["r", "p"].includes(table.relkind ?? ""));
|
|
11649
|
+
if (unsupported.length > 0) {
|
|
11650
|
+
throw new Error(`PowerSync publication only accepts ordinary or partitioned tables: ${unsupported.map((table) => table.name).join(", ")}`);
|
|
11413
11651
|
}
|
|
11414
11652
|
}
|
|
11415
11653
|
async function configureReplicationRole(tx, passwordValue) {
|
|
@@ -11617,7 +11855,7 @@ class PgWireClient {
|
|
|
11617
11855
|
return client;
|
|
11618
11856
|
}
|
|
11619
11857
|
open(opts) {
|
|
11620
|
-
return new Promise((
|
|
11858
|
+
return new Promise((resolve, reject) => {
|
|
11621
11859
|
let startupComplete = false;
|
|
11622
11860
|
this.socket = opts.socketPath ? createConnection(opts.socketPath) : createConnection(opts.port ?? 5432, opts.host ?? "127.0.0.1");
|
|
11623
11861
|
this.socket.setTimeout(1e4, () => this.socket.destroy(new Error("PostgreSQL handshake timed out")));
|
|
@@ -11721,7 +11959,7 @@ class PgWireClient {
|
|
|
11721
11959
|
this.buffer = Buffer.concat([this.buffer, c]);
|
|
11722
11960
|
this.processMessages();
|
|
11723
11961
|
});
|
|
11724
|
-
|
|
11962
|
+
resolve();
|
|
11725
11963
|
return;
|
|
11726
11964
|
}
|
|
11727
11965
|
}
|
|
@@ -11730,10 +11968,10 @@ class PgWireClient {
|
|
|
11730
11968
|
});
|
|
11731
11969
|
}
|
|
11732
11970
|
run(send) {
|
|
11733
|
-
const op = this.queue.then(() => new Promise((
|
|
11971
|
+
const op = this.queue.then(() => new Promise((resolve, reject) => {
|
|
11734
11972
|
if (this.closed)
|
|
11735
11973
|
return reject(new Error("connection closed"));
|
|
11736
|
-
this.pending = { resolve
|
|
11974
|
+
this.pending = { resolve, reject, results: [], columns: [], error: null };
|
|
11737
11975
|
send();
|
|
11738
11976
|
}));
|
|
11739
11977
|
this.queue = op.catch(() => {});
|
|
@@ -11764,11 +12002,11 @@ class PgWireClient {
|
|
|
11764
12002
|
return results[0] ?? { rows: [] };
|
|
11765
12003
|
}
|
|
11766
12004
|
close() {
|
|
11767
|
-
return new Promise((
|
|
12005
|
+
return new Promise((resolve) => {
|
|
11768
12006
|
if (this.closed)
|
|
11769
|
-
return
|
|
12007
|
+
return resolve();
|
|
11770
12008
|
this.socket.write(message(88, Buffer.alloc(0)));
|
|
11771
|
-
this.socket.end(() =>
|
|
12009
|
+
this.socket.end(() => resolve());
|
|
11772
12010
|
});
|
|
11773
12011
|
}
|
|
11774
12012
|
nextMessage() {
|
|
@@ -11939,10 +12177,10 @@ function decodeValue(text, oid) {
|
|
|
11939
12177
|
case 1114:
|
|
11940
12178
|
return new Date(text.replace(" ", "T") + "Z");
|
|
11941
12179
|
case 1184: {
|
|
11942
|
-
let
|
|
11943
|
-
if (/[+-]\d\d$/.test(
|
|
11944
|
-
|
|
11945
|
-
return new Date(
|
|
12180
|
+
let iso = text.replace(" ", "T");
|
|
12181
|
+
if (/[+-]\d\d$/.test(iso))
|
|
12182
|
+
iso += ":00";
|
|
12183
|
+
return new Date(iso);
|
|
11946
12184
|
}
|
|
11947
12185
|
case 1000:
|
|
11948
12186
|
return parsePgArray(text).map((v) => v === "t");
|
|
@@ -12031,9 +12269,9 @@ async function buildWireEngine(options) {
|
|
|
12031
12269
|
return queryMutex.run(async () => {
|
|
12032
12270
|
await queryClient.exec("begin");
|
|
12033
12271
|
try {
|
|
12034
|
-
const
|
|
12272
|
+
const response = await callback(transactionClient);
|
|
12035
12273
|
await queryClient.exec("commit");
|
|
12036
|
-
return
|
|
12274
|
+
return response;
|
|
12037
12275
|
} catch (error) {
|
|
12038
12276
|
await queryClient.exec("rollback").catch(() => {});
|
|
12039
12277
|
throw error;
|
|
@@ -12262,15 +12500,15 @@ async function fetchRelease(url) {
|
|
|
12262
12500
|
let lastError;
|
|
12263
12501
|
for (let attempt = 1;attempt <= 3; attempt++) {
|
|
12264
12502
|
try {
|
|
12265
|
-
const
|
|
12266
|
-
if (
|
|
12267
|
-
return
|
|
12268
|
-
lastError = new Error(`failed to download ${url}: HTTP ${
|
|
12503
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(30000) });
|
|
12504
|
+
if (response.ok || response.status < 500)
|
|
12505
|
+
return response;
|
|
12506
|
+
lastError = new Error(`failed to download ${url}: HTTP ${response.status}`);
|
|
12269
12507
|
} catch (error) {
|
|
12270
12508
|
lastError = error;
|
|
12271
12509
|
}
|
|
12272
12510
|
if (attempt < 3)
|
|
12273
|
-
await new Promise((
|
|
12511
|
+
await new Promise((resolve) => setTimeout(resolve, attempt * 500));
|
|
12274
12512
|
}
|
|
12275
12513
|
throw lastError instanceof Error ? lastError : new Error(`failed to download ${url}`);
|
|
12276
12514
|
}
|
|
@@ -12362,7 +12600,7 @@ ${detail}` : " (no output)"}
|
|
|
12362
12600
|
` + `data dir: ${opts.dataDir}
|
|
12363
12601
|
` + "If a previous run is still holding it, stop it; or delete the data dir to start fresh.");
|
|
12364
12602
|
}
|
|
12365
|
-
await new Promise((
|
|
12603
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
12366
12604
|
}
|
|
12367
12605
|
}
|
|
12368
12606
|
throw new Error(`timed out waiting for embedded postgres at ${socketPath}`);
|
|
@@ -12390,14 +12628,14 @@ async function stopPostgres(postgres, hasExited) {
|
|
|
12390
12628
|
if (hasExited())
|
|
12391
12629
|
return;
|
|
12392
12630
|
postgres.kill("SIGINT");
|
|
12393
|
-
await new Promise((
|
|
12631
|
+
await new Promise((resolve) => {
|
|
12394
12632
|
const killTimeout = setTimeout(() => {
|
|
12395
12633
|
postgres.kill("SIGKILL");
|
|
12396
|
-
|
|
12634
|
+
resolve();
|
|
12397
12635
|
}, 5000);
|
|
12398
12636
|
postgres.once("exit", () => {
|
|
12399
12637
|
clearTimeout(killTimeout);
|
|
12400
|
-
|
|
12638
|
+
resolve();
|
|
12401
12639
|
});
|
|
12402
12640
|
});
|
|
12403
12641
|
}
|
|
@@ -12693,7 +12931,7 @@ function isFixedSearchPath(searchPath) {
|
|
|
12693
12931
|
function reconcileModule(module, catalog) {
|
|
12694
12932
|
const issues = [];
|
|
12695
12933
|
const ownedTables = new Set(module.tables);
|
|
12696
|
-
const push = (severity, code, object,
|
|
12934
|
+
const push = (severity, code, object, message) => issues.push({ severity, code, object, message });
|
|
12697
12935
|
for (const policy of module.policies) {
|
|
12698
12936
|
const [schema, table] = splitQualifiedName(policy.table);
|
|
12699
12937
|
const found = catalog.policies.some((cp) => cp.schema === schema && cp.table === table && cp.name === policy.name);
|
|
@@ -12825,7 +13063,7 @@ function lintSql(sql, file) {
|
|
|
12825
13063
|
}
|
|
12826
13064
|
return issues;
|
|
12827
13065
|
}
|
|
12828
|
-
async function lintModule(module,
|
|
13066
|
+
async function lintModule(module, readFile) {
|
|
12829
13067
|
const issues = [];
|
|
12830
13068
|
const sources = new Set;
|
|
12831
13069
|
for (const decl of [
|
|
@@ -12838,7 +13076,7 @@ async function lintModule(module, readFile2) {
|
|
|
12838
13076
|
}
|
|
12839
13077
|
const contents = new Map;
|
|
12840
13078
|
await Promise.all([...sources].map(async (path) => {
|
|
12841
|
-
contents.set(path, await
|
|
13079
|
+
contents.set(path, await readFile(path));
|
|
12842
13080
|
}));
|
|
12843
13081
|
for (const [file, sql] of contents) {
|
|
12844
13082
|
issues.push(...lintSql(sql, file));
|
|
@@ -12894,12 +13132,12 @@ function keys(row, expected) {
|
|
|
12894
13132
|
}
|
|
12895
13133
|
}
|
|
12896
13134
|
function validTarget(value) {
|
|
12897
|
-
const
|
|
12898
|
-
keys(
|
|
12899
|
-
if (typeof
|
|
13135
|
+
const target = record(value);
|
|
13136
|
+
keys(target, ["environment", "projectRef"]);
|
|
13137
|
+
if (typeof target.environment !== "string" || !/^[a-z][a-z0-9-]{0,62}$/.test(target.environment) || typeof target.projectRef !== "string" || !/^[a-z0-9][a-z0-9_-]{0,62}$/.test(target.projectRef)) {
|
|
12900
13138
|
throw new Error("Invalid migration binding target");
|
|
12901
13139
|
}
|
|
12902
|
-
return
|
|
13140
|
+
return target;
|
|
12903
13141
|
}
|
|
12904
13142
|
function validFile(file) {
|
|
12905
13143
|
return typeof file === "string" && /^[a-zA-Z0-9_./-]+\.sql$/.test(file) && !file.startsWith("/") && file.split("/").every((part) => part && part !== "." && part !== "..");
|
|
@@ -12911,7 +13149,7 @@ function parseMigrationBindingManifest(value) {
|
|
|
12911
13149
|
throw new Error("Invalid migration binding manifest");
|
|
12912
13150
|
}
|
|
12913
13151
|
const targets = manifest.targets.map(validTarget);
|
|
12914
|
-
if (new Set(targets.map((
|
|
13152
|
+
if (new Set(targets.map((target) => target.environment)).size !== targets.length) {
|
|
12915
13153
|
throw new Error("Duplicate migration binding environment");
|
|
12916
13154
|
}
|
|
12917
13155
|
const files = new Set;
|
|
@@ -12958,8 +13196,8 @@ function bindingValue(parameter, values) {
|
|
|
12958
13196
|
}
|
|
12959
13197
|
function renderMigrationBindings(options) {
|
|
12960
13198
|
const manifest = parseMigrationBindingManifest(options.manifest);
|
|
12961
|
-
const
|
|
12962
|
-
if (!manifest.targets.some((candidate) => candidate.environment ===
|
|
13199
|
+
const target = validTarget(options.target);
|
|
13200
|
+
if (!manifest.targets.some((candidate) => candidate.environment === target.environment && candidate.projectRef === target.projectRef)) {
|
|
12963
13201
|
throw new Error("Migration binding target is not registered in the manifest");
|
|
12964
13202
|
}
|
|
12965
13203
|
const sources = new Map;
|
|
@@ -13014,7 +13252,7 @@ function renderMigrationBindings(options) {
|
|
|
13014
13252
|
migrations,
|
|
13015
13253
|
attestation: {
|
|
13016
13254
|
schema: "supacloud.migration-binding-attestation.v1",
|
|
13017
|
-
...
|
|
13255
|
+
...target,
|
|
13018
13256
|
manifestSha256: migrationBindingSha256(JSON.stringify(manifest)),
|
|
13019
13257
|
files: files.sort((left, right) => left.file.localeCompare(right.file))
|
|
13020
13258
|
}
|
|
@@ -13432,8 +13670,8 @@ function getDurationSeconds(table, key) {
|
|
|
13432
13670
|
return;
|
|
13433
13671
|
const m = v.trim().match(/^(\d+)\s*(s|m|h)$/i);
|
|
13434
13672
|
if (!m) {
|
|
13435
|
-
const
|
|
13436
|
-
return Number.isFinite(
|
|
13673
|
+
const n = parseInt(v, 10);
|
|
13674
|
+
return Number.isFinite(n) ? n : undefined;
|
|
13437
13675
|
}
|
|
13438
13676
|
const n = parseInt(m[1], 10);
|
|
13439
13677
|
const unit = m[2].toLowerCase();
|
|
@@ -13711,9 +13949,9 @@ function readFunctions(root, mode) {
|
|
|
13711
13949
|
const enabled = getBool(t, "enabled");
|
|
13712
13950
|
if (enabled !== undefined)
|
|
13713
13951
|
opts.enabled = enabled;
|
|
13714
|
-
const
|
|
13715
|
-
if (
|
|
13716
|
-
opts.verifyJwt =
|
|
13952
|
+
const verifyJwt = getBool(t, "verify_jwt");
|
|
13953
|
+
if (verifyJwt !== undefined)
|
|
13954
|
+
opts.verifyJwt = verifyJwt;
|
|
13717
13955
|
const entrypoint = getString(t, "entrypoint");
|
|
13718
13956
|
if (entrypoint !== undefined)
|
|
13719
13957
|
opts.entrypoint = entrypoint;
|
|
@@ -13891,8 +14129,8 @@ async function serveBun(backend, opts = {}) {
|
|
|
13891
14129
|
}, { vsn: ws.data.vsn });
|
|
13892
14130
|
ws.data.session = session;
|
|
13893
14131
|
},
|
|
13894
|
-
message(ws,
|
|
13895
|
-
ws.data.session?.onMessage(typeof
|
|
14132
|
+
message(ws, message) {
|
|
14133
|
+
ws.data.session?.onMessage(typeof message === "string" ? message : new Uint8Array(message));
|
|
13896
14134
|
},
|
|
13897
14135
|
close(ws) {
|
|
13898
14136
|
ws.data.session?.onClose();
|
|
@@ -13953,8 +14191,8 @@ class FsStorageDriver {
|
|
|
13953
14191
|
async delete(key) {
|
|
13954
14192
|
await rm2(this.resolve(key), { force: true });
|
|
13955
14193
|
}
|
|
13956
|
-
async deleteMany(
|
|
13957
|
-
for (const k of
|
|
14194
|
+
async deleteMany(keys) {
|
|
14195
|
+
for (const k of keys)
|
|
13958
14196
|
await this.delete(k);
|
|
13959
14197
|
}
|
|
13960
14198
|
}
|
|
@@ -14283,8 +14521,8 @@ class S3StorageDriver {
|
|
|
14283
14521
|
async delete(key) {
|
|
14284
14522
|
await this.client.file(this.objectKey(key)).delete();
|
|
14285
14523
|
}
|
|
14286
|
-
async deleteMany(
|
|
14287
|
-
for (const key of
|
|
14524
|
+
async deleteMany(keys) {
|
|
14525
|
+
for (const key of keys) {
|
|
14288
14526
|
await this.delete(key);
|
|
14289
14527
|
}
|
|
14290
14528
|
}
|
|
@@ -14334,12 +14572,12 @@ async function assertResetPathsSafe(paths) {
|
|
|
14334
14572
|
["storage", paths.storageDir]
|
|
14335
14573
|
];
|
|
14336
14574
|
for (const [label, targetPath] of targets) {
|
|
14337
|
-
const
|
|
14338
|
-
const relativePath = relative(stateDir,
|
|
14575
|
+
const target = resolve4(targetPath);
|
|
14576
|
+
const relativePath = relative(stateDir, target);
|
|
14339
14577
|
if (!relativePath || relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
14340
|
-
throw new Error(`refusing to reset ${label} path outside the state directory: ${
|
|
14578
|
+
throw new Error(`refusing to reset ${label} path outside the state directory: ${target}`);
|
|
14341
14579
|
}
|
|
14342
|
-
await assertResetTargetCanonical(stateDir, canonicalStateDir,
|
|
14580
|
+
await assertResetTargetCanonical(stateDir, canonicalStateDir, target, label);
|
|
14343
14581
|
}
|
|
14344
14582
|
}
|
|
14345
14583
|
async function requiredResetEntry(path) {
|
|
@@ -14366,8 +14604,8 @@ async function assertResetSecretsValid(path) {
|
|
|
14366
14604
|
throw new Error(RESET_INVALID_SECRETS_ERROR);
|
|
14367
14605
|
}
|
|
14368
14606
|
}
|
|
14369
|
-
async function assertResetTargetCanonical(stateDir, canonicalStateDir,
|
|
14370
|
-
let current =
|
|
14607
|
+
async function assertResetTargetCanonical(stateDir, canonicalStateDir, target, label) {
|
|
14608
|
+
let current = target;
|
|
14371
14609
|
while (current !== stateDir) {
|
|
14372
14610
|
try {
|
|
14373
14611
|
if ((await lstat2(current)).isSymbolicLink()) {
|
|
@@ -14379,18 +14617,18 @@ async function assertResetTargetCanonical(stateDir, canonicalStateDir, target2,
|
|
|
14379
14617
|
}
|
|
14380
14618
|
const parent = dirname6(current);
|
|
14381
14619
|
if (parent === current)
|
|
14382
|
-
throw new Error(`refusing to reset ${label} path outside the state directory: ${
|
|
14620
|
+
throw new Error(`refusing to reset ${label} path outside the state directory: ${target}`);
|
|
14383
14621
|
current = parent;
|
|
14384
14622
|
}
|
|
14385
|
-
const existingAncestor = await nearestExistingAncestor(
|
|
14386
|
-
const canonicalTarget = resolve4(await realpath2(existingAncestor), relative(existingAncestor,
|
|
14623
|
+
const existingAncestor = await nearestExistingAncestor(target);
|
|
14624
|
+
const canonicalTarget = resolve4(await realpath2(existingAncestor), relative(existingAncestor, target));
|
|
14387
14625
|
const canonicalRelative = relative(canonicalStateDir, canonicalTarget);
|
|
14388
14626
|
if (!canonicalRelative || canonicalRelative.startsWith("..") || isAbsolute(canonicalRelative)) {
|
|
14389
|
-
throw new Error(`refusing to reset ${label} path outside the canonical state directory: ${
|
|
14627
|
+
throw new Error(`refusing to reset ${label} path outside the canonical state directory: ${target}`);
|
|
14390
14628
|
}
|
|
14391
14629
|
}
|
|
14392
|
-
async function nearestExistingAncestor(
|
|
14393
|
-
let current =
|
|
14630
|
+
async function nearestExistingAncestor(target) {
|
|
14631
|
+
let current = target;
|
|
14394
14632
|
while (true) {
|
|
14395
14633
|
try {
|
|
14396
14634
|
await lstat2(current);
|
|
@@ -14401,7 +14639,7 @@ async function nearestExistingAncestor(target2) {
|
|
|
14401
14639
|
}
|
|
14402
14640
|
const parent = dirname6(current);
|
|
14403
14641
|
if (parent === current)
|
|
14404
|
-
throw new Error(`unable to resolve an existing ancestor for ${
|
|
14642
|
+
throw new Error(`unable to resolve an existing ancestor for ${target}`);
|
|
14405
14643
|
current = parent;
|
|
14406
14644
|
}
|
|
14407
14645
|
}
|
|
@@ -14429,14 +14667,14 @@ async function ensureProjectSecrets(paths) {
|
|
|
14429
14667
|
`, { mode: 384, flag: "wx" });
|
|
14430
14668
|
await link(temporaryFile, paths.secretsFile);
|
|
14431
14669
|
stored = candidate;
|
|
14432
|
-
} catch (
|
|
14433
|
-
if (
|
|
14434
|
-
throw
|
|
14670
|
+
} catch (error) {
|
|
14671
|
+
if (error.code !== "EEXIST")
|
|
14672
|
+
throw error;
|
|
14435
14673
|
stored = validateSecrets(JSON.parse(await readFile7(paths.secretsFile, "utf8")));
|
|
14436
14674
|
} finally {
|
|
14437
|
-
await unlink2(temporaryFile).catch((
|
|
14438
|
-
if (
|
|
14439
|
-
throw
|
|
14675
|
+
await unlink2(temporaryFile).catch((error) => {
|
|
14676
|
+
if (error.code !== "ENOENT")
|
|
14677
|
+
throw error;
|
|
14440
14678
|
});
|
|
14441
14679
|
}
|
|
14442
14680
|
}
|
|
@@ -14938,10 +15176,10 @@ async function stageDirectory(root, destination) {
|
|
|
14938
15176
|
throw error;
|
|
14939
15177
|
}
|
|
14940
15178
|
await mkdir6(destination, { recursive: true });
|
|
14941
|
-
const walk = async (current,
|
|
15179
|
+
const walk = async (current, target) => {
|
|
14942
15180
|
for (const entry of await readdir3(current, { withFileTypes: true })) {
|
|
14943
15181
|
const fullPath = join9(current, entry.name);
|
|
14944
|
-
const targetPath = join9(
|
|
15182
|
+
const targetPath = join9(target, entry.name);
|
|
14945
15183
|
if (entry.isSymbolicLink())
|
|
14946
15184
|
throw new Error(`snapshot refuses symbolic link: ${fullPath}`);
|
|
14947
15185
|
if (entry.isDirectory()) {
|
|
@@ -14956,9 +15194,9 @@ async function stageDirectory(root, destination) {
|
|
|
14956
15194
|
};
|
|
14957
15195
|
await walk(root, destination);
|
|
14958
15196
|
}
|
|
14959
|
-
async function stageFile(source,
|
|
14960
|
-
await mkdir6(dirname7(
|
|
14961
|
-
await copyFile(source,
|
|
15197
|
+
async function stageFile(source, target) {
|
|
15198
|
+
await mkdir6(dirname7(target), { recursive: true });
|
|
15199
|
+
await copyFile(source, target);
|
|
14962
15200
|
}
|
|
14963
15201
|
async function readManifest(payloadRoot) {
|
|
14964
15202
|
let parsed;
|
|
@@ -15030,9 +15268,9 @@ async function assertRestoreTargets(paths, manifest, force) {
|
|
|
15030
15268
|
if (manifest.includesLocalStorage && !isWithin(paths.stateDir, paths.storageDir))
|
|
15031
15269
|
targets.push(paths.storageDir);
|
|
15032
15270
|
if (!force) {
|
|
15033
|
-
for (const
|
|
15034
|
-
if (await directoryHasEntries(
|
|
15035
|
-
throw new Error(`restore target is not empty: ${
|
|
15271
|
+
for (const target of targets) {
|
|
15272
|
+
if (await directoryHasEntries(target))
|
|
15273
|
+
throw new Error(`restore target is not empty: ${target}; pass --force to replace it`);
|
|
15036
15274
|
}
|
|
15037
15275
|
}
|
|
15038
15276
|
}
|
|
@@ -15045,28 +15283,28 @@ async function directoryHasEntries(path) {
|
|
|
15045
15283
|
throw error;
|
|
15046
15284
|
}
|
|
15047
15285
|
}
|
|
15048
|
-
async function applyDirectorySwap(source,
|
|
15049
|
-
const targetInfo = await existingInfo(
|
|
15286
|
+
async function applyDirectorySwap(source, target, force, rollbackId, swaps) {
|
|
15287
|
+
const targetInfo = await existingInfo(target);
|
|
15050
15288
|
if (targetInfo && !targetInfo.isDirectory())
|
|
15051
|
-
throw new Error(`restore target is not a directory: ${
|
|
15052
|
-
const swap = { target
|
|
15289
|
+
throw new Error(`restore target is not a directory: ${target}`);
|
|
15290
|
+
const swap = { target };
|
|
15053
15291
|
if (targetInfo) {
|
|
15054
15292
|
if (!force) {
|
|
15055
|
-
if (await directoryHasEntries(
|
|
15056
|
-
throw new Error(`restore target is not empty: ${
|
|
15057
|
-
await rm5(
|
|
15293
|
+
if (await directoryHasEntries(target))
|
|
15294
|
+
throw new Error(`restore target is not empty: ${target}; pass --force to replace it`);
|
|
15295
|
+
await rm5(target, { recursive: true, force: true });
|
|
15058
15296
|
} else {
|
|
15059
|
-
swap.rollbackPath = join9(dirname7(
|
|
15060
|
-
await rename2(
|
|
15297
|
+
swap.rollbackPath = join9(dirname7(target), `.${target.split(sep2).pop() ?? "state"}.restore-${rollbackId}`);
|
|
15298
|
+
await rename2(target, swap.rollbackPath);
|
|
15061
15299
|
}
|
|
15062
15300
|
}
|
|
15063
15301
|
try {
|
|
15064
|
-
await mkdir6(dirname7(
|
|
15065
|
-
await rename2(source,
|
|
15302
|
+
await mkdir6(dirname7(target), { recursive: true });
|
|
15303
|
+
await rename2(source, target);
|
|
15066
15304
|
swaps.push(swap);
|
|
15067
15305
|
} catch (error) {
|
|
15068
15306
|
if (swap.rollbackPath)
|
|
15069
|
-
await rename2(swap.rollbackPath,
|
|
15307
|
+
await rename2(swap.rollbackPath, target).catch(() => {});
|
|
15070
15308
|
throw error;
|
|
15071
15309
|
}
|
|
15072
15310
|
}
|
|
@@ -15086,17 +15324,17 @@ async function existingInfo(path) {
|
|
|
15086
15324
|
throw error;
|
|
15087
15325
|
}
|
|
15088
15326
|
}
|
|
15089
|
-
async function copyEntry(source,
|
|
15327
|
+
async function copyEntry(source, target) {
|
|
15090
15328
|
const info = await lstat3(source);
|
|
15091
15329
|
if (info.isSymbolicLink())
|
|
15092
15330
|
throw new Error(`snapshot refuses symbolic link: ${source}`);
|
|
15093
15331
|
if (info.isDirectory()) {
|
|
15094
|
-
await mkdir6(
|
|
15332
|
+
await mkdir6(target, { recursive: true });
|
|
15095
15333
|
for (const entry of await readdir3(source))
|
|
15096
|
-
await copyEntry(join9(source, entry), join9(
|
|
15334
|
+
await copyEntry(join9(source, entry), join9(target, entry));
|
|
15097
15335
|
} else if (info.isFile()) {
|
|
15098
|
-
await mkdir6(dirname7(
|
|
15099
|
-
await Bun.write(
|
|
15336
|
+
await mkdir6(dirname7(target), { recursive: true });
|
|
15337
|
+
await Bun.write(target, Bun.file(source));
|
|
15100
15338
|
} else
|
|
15101
15339
|
throw new Error(`snapshot refuses unsupported filesystem entry: ${source}`);
|
|
15102
15340
|
}
|
|
@@ -15251,13 +15489,13 @@ async function main() {
|
|
|
15251
15489
|
}
|
|
15252
15490
|
if (options.command === "keys") {
|
|
15253
15491
|
const secrets = await ensureProjectSecrets(paths);
|
|
15254
|
-
const
|
|
15492
|
+
const keys = await mintProjectKeys(secrets.jwtSecret);
|
|
15255
15493
|
const privilegedKey = options.serviceRole ? `
|
|
15256
15494
|
service_role key:
|
|
15257
|
-
${
|
|
15495
|
+
${keys.serviceRoleKey}` : `
|
|
15258
15496
|
Use --service-role to print the privileged key.`;
|
|
15259
15497
|
await writeStandardOutput(`anon key:
|
|
15260
|
-
${
|
|
15498
|
+
${keys.anonKey}
|
|
15261
15499
|
${privilegedKey}
|
|
15262
15500
|
`);
|
|
15263
15501
|
return;
|
|
@@ -15278,7 +15516,7 @@ ${privilegedKey}
|
|
|
15278
15516
|
if (options.positionals[0] && options.positionals[0] !== "types" && options.positionals[0] !== "typescript") {
|
|
15279
15517
|
throw new Error(`unknown gen subcommand: ${options.positionals[0]}`);
|
|
15280
15518
|
}
|
|
15281
|
-
const
|
|
15519
|
+
const project = await createProjectBackend({
|
|
15282
15520
|
...options,
|
|
15283
15521
|
applyMigrations: false,
|
|
15284
15522
|
includeFunctions: false,
|
|
@@ -15289,7 +15527,7 @@ ${privilegedKey}
|
|
|
15289
15527
|
log: quietLog
|
|
15290
15528
|
});
|
|
15291
15529
|
try {
|
|
15292
|
-
const source = await generateTypes(
|
|
15530
|
+
const source = await generateTypes(project.backend.db, "public");
|
|
15293
15531
|
if (options.output) {
|
|
15294
15532
|
await mkdir7(dirname8(options.output), { recursive: true });
|
|
15295
15533
|
await writeFile7(options.output, source);
|
|
@@ -15298,12 +15536,12 @@ ${privilegedKey}
|
|
|
15298
15536
|
} else
|
|
15299
15537
|
await writeStandardOutput(source);
|
|
15300
15538
|
} finally {
|
|
15301
|
-
await
|
|
15539
|
+
await project.backend.close();
|
|
15302
15540
|
}
|
|
15303
15541
|
return;
|
|
15304
15542
|
}
|
|
15305
15543
|
if (options.command === "inspect") {
|
|
15306
|
-
const
|
|
15544
|
+
const project = await createProjectBackend({
|
|
15307
15545
|
...options,
|
|
15308
15546
|
applyMigrations: false,
|
|
15309
15547
|
includeFunctions: false,
|
|
@@ -15314,9 +15552,9 @@ ${privilegedKey}
|
|
|
15314
15552
|
log: quietLog
|
|
15315
15553
|
});
|
|
15316
15554
|
try {
|
|
15317
|
-
await printInspection(await inspectDb(
|
|
15555
|
+
await printInspection(await inspectDb(project.backend.db, "public"));
|
|
15318
15556
|
} finally {
|
|
15319
|
-
await
|
|
15557
|
+
await project.backend.close();
|
|
15320
15558
|
}
|
|
15321
15559
|
return;
|
|
15322
15560
|
}
|
|
@@ -15354,7 +15592,7 @@ ${privilegedKey}
|
|
|
15354
15592
|
return;
|
|
15355
15593
|
}
|
|
15356
15594
|
if (options.command === "migrate" || options.command === "status") {
|
|
15357
|
-
const
|
|
15595
|
+
const project = await createProjectBackend({
|
|
15358
15596
|
...options,
|
|
15359
15597
|
applyMigrations: options.command === "migrate",
|
|
15360
15598
|
includeFunctions: false,
|
|
@@ -15366,13 +15604,13 @@ ${privilegedKey}
|
|
|
15366
15604
|
log: quietLog
|
|
15367
15605
|
});
|
|
15368
15606
|
try {
|
|
15369
|
-
const applied = await
|
|
15607
|
+
const applied = await project.backend.db.listAppliedMigrations();
|
|
15370
15608
|
if (options.json) {
|
|
15371
15609
|
await writeStandardOutput(`${JSON.stringify({
|
|
15372
15610
|
schema: "supacloud.lite-migrations.v1",
|
|
15373
15611
|
command: options.command,
|
|
15374
15612
|
applied,
|
|
15375
|
-
binding_attestation:
|
|
15613
|
+
binding_attestation: project.bindingAttestation ?? null
|
|
15376
15614
|
}, null, 2)}
|
|
15377
15615
|
`);
|
|
15378
15616
|
return;
|
|
@@ -15382,13 +15620,13 @@ ${privilegedKey}
|
|
|
15382
15620
|
await writeStandardOutput(`${output}
|
|
15383
15621
|
`);
|
|
15384
15622
|
} finally {
|
|
15385
|
-
await
|
|
15623
|
+
await project.backend.close();
|
|
15386
15624
|
}
|
|
15387
15625
|
return;
|
|
15388
15626
|
}
|
|
15389
15627
|
if (options.command !== "start")
|
|
15390
15628
|
throw new Error(`unknown command: ${options.command}`);
|
|
15391
|
-
const project = await startProjectServer({ ...options, log: (
|
|
15629
|
+
const project = await startProjectServer({ ...options, log: (message) => console.log(` ${message}`) });
|
|
15392
15630
|
const shutdown = waitForShutdown(() => project.close());
|
|
15393
15631
|
await writeStandardOutput(`
|
|
15394
15632
|
SupaCloud Lite running
|
|
@@ -15420,7 +15658,7 @@ async function runDbCommand(options) {
|
|
|
15420
15658
|
if (paths.dataDir)
|
|
15421
15659
|
await rm6(paths.dataDir, { recursive: true, force: true });
|
|
15422
15660
|
await rm6(paths.storageDir, { recursive: true, force: true });
|
|
15423
|
-
const
|
|
15661
|
+
const project = await createProjectBackend({
|
|
15424
15662
|
...options,
|
|
15425
15663
|
includeFunctions: false,
|
|
15426
15664
|
includeWebhooks: false,
|
|
@@ -15430,11 +15668,11 @@ async function runDbCommand(options) {
|
|
|
15430
15668
|
log: quietLog
|
|
15431
15669
|
});
|
|
15432
15670
|
try {
|
|
15433
|
-
const applied = await
|
|
15671
|
+
const applied = await project.backend.db.listAppliedMigrations();
|
|
15434
15672
|
await writeStandardOutput(`reset complete: ${applied.length} migration(s) applied
|
|
15435
15673
|
`);
|
|
15436
15674
|
} finally {
|
|
15437
|
-
await
|
|
15675
|
+
await project.backend.close();
|
|
15438
15676
|
}
|
|
15439
15677
|
return;
|
|
15440
15678
|
}
|
|
@@ -15504,7 +15742,7 @@ async function runDbCommand(options) {
|
|
|
15504
15742
|
}
|
|
15505
15743
|
if (subcommand === "check") {
|
|
15506
15744
|
const moduleFile = options.moduleFile ?? join10(paths.projectDir, "supabase", "db", "modules.ts");
|
|
15507
|
-
const
|
|
15745
|
+
const project = await createProjectBackend({
|
|
15508
15746
|
...options,
|
|
15509
15747
|
applyMigrations: false,
|
|
15510
15748
|
includeFunctions: false,
|
|
@@ -15516,14 +15754,14 @@ async function runDbCommand(options) {
|
|
|
15516
15754
|
});
|
|
15517
15755
|
try {
|
|
15518
15756
|
const executor = {
|
|
15519
|
-
query: async (sql, params) => (await
|
|
15757
|
+
query: async (sql, params) => (await project.backend.db.query(sql, params)).rows
|
|
15520
15758
|
};
|
|
15521
15759
|
const result = await checkDatabaseModules({ moduleFile, executor });
|
|
15522
15760
|
await writeStandardOutput(formatDatabaseModuleCheck(result));
|
|
15523
15761
|
if (!result.ok)
|
|
15524
15762
|
throw new Error("db check found error-level issues");
|
|
15525
15763
|
} finally {
|
|
15526
|
-
await
|
|
15764
|
+
await project.backend.close();
|
|
15527
15765
|
}
|
|
15528
15766
|
return;
|
|
15529
15767
|
}
|