@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/index.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var __require = import.meta.require;
|
|
3
|
-
|
|
4
2
|
// src/runtime/jwt.ts
|
|
5
3
|
var encoder = new TextEncoder;
|
|
6
4
|
var decoder = new TextDecoder;
|
|
@@ -85,7 +83,7 @@ function randomToken(bytes = 32) {
|
|
|
85
83
|
// package.json
|
|
86
84
|
var package_default = {
|
|
87
85
|
name: "@supacloud/lite",
|
|
88
|
-
version: "0.
|
|
86
|
+
version: "0.16.0",
|
|
89
87
|
description: "Bun-native, single-project Supabase-compatible backend powered by PGlite or native PostgreSQL",
|
|
90
88
|
type: "module",
|
|
91
89
|
license: "Apache-2.0",
|
|
@@ -145,13 +143,13 @@ var package_default = {
|
|
|
145
143
|
"@supacloud/compiler": "^0.13.1",
|
|
146
144
|
"@supacloud/elysia": "^0.10.0",
|
|
147
145
|
"@supabase/supabase-js": "^2.115.0",
|
|
148
|
-
"@types/bun": "^1.4.
|
|
146
|
+
"@types/bun": "^1.4.2",
|
|
149
147
|
elysia: "^1.4.30",
|
|
150
148
|
jose: "^6.2.11",
|
|
151
149
|
typescript: "^7.0.2"
|
|
152
150
|
},
|
|
153
151
|
engines: {
|
|
154
|
-
bun: ">=1.4.
|
|
152
|
+
bun: ">=1.4.2"
|
|
155
153
|
},
|
|
156
154
|
publishConfig: {
|
|
157
155
|
access: "public",
|
|
@@ -427,10 +425,15 @@ class OAuthService {
|
|
|
427
425
|
JSON.stringify({ provider, providers: [provider] }),
|
|
428
426
|
JSON.stringify({ sub: profile.id, email: profile.email, full_name: profile.name, ...profile.metadata })
|
|
429
427
|
]);
|
|
430
|
-
|
|
428
|
+
const user = created.rows[0];
|
|
429
|
+
if (!user)
|
|
430
|
+
throw new Error("OAuth user creation returned no row");
|
|
431
|
+
userId = user.id;
|
|
431
432
|
}
|
|
432
433
|
await this.db.query(`insert into auth.identities (user_id, provider, provider_id, identity_data)
|
|
433
434
|
values ($1, $2, $3, $4::jsonb)`, [userId, provider, profile.id, JSON.stringify({ sub: profile.id, email: profile.email, ...profile.metadata })]);
|
|
435
|
+
if (!userId)
|
|
436
|
+
throw new Error("OAuth identity has no user ID");
|
|
434
437
|
return userId;
|
|
435
438
|
}
|
|
436
439
|
}
|
|
@@ -643,10 +646,10 @@ class QrCode {
|
|
|
643
646
|
const bytes = new TextEncoder().encode(text);
|
|
644
647
|
let version = 1;
|
|
645
648
|
for (;version <= 40; version++) {
|
|
646
|
-
const
|
|
649
|
+
const dataCapacityBits = getNumDataCodewords(version) * 8;
|
|
647
650
|
const ccBits = version < 10 ? 8 : 16;
|
|
648
651
|
const usedBits = 4 + ccBits + bytes.length * 8;
|
|
649
|
-
if (usedBits <=
|
|
652
|
+
if (usedBits <= dataCapacityBits)
|
|
650
653
|
break;
|
|
651
654
|
}
|
|
652
655
|
if (version > 40)
|
|
@@ -1338,10 +1341,10 @@ class AuthHandler {
|
|
|
1338
1341
|
if (!this.settings.anonymousUsers) {
|
|
1339
1342
|
return authError(422, "anonymous_provider_disabled", "Anonymous sign-ins are disabled");
|
|
1340
1343
|
}
|
|
1341
|
-
const
|
|
1344
|
+
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)
|
|
1342
1345
|
values ('authenticated', 'authenticated', '{}', $1, true, now())
|
|
1343
1346
|
returning *`, [JSON.stringify(body.data ?? {})]);
|
|
1344
|
-
return json(200, await this.sessionFor(
|
|
1347
|
+
return json(200, await this.sessionFor(res.rows[0]));
|
|
1345
1348
|
}
|
|
1346
1349
|
if (this.settings.disableSignup) {
|
|
1347
1350
|
return authError(422, "signup_disabled", "Signups not allowed for this instance");
|
|
@@ -3935,45 +3938,202 @@ AS $$
|
|
|
3935
3938
|
DECLARE
|
|
3936
3939
|
normalized_queue_name text := lower(btrim(queue_name));
|
|
3937
3940
|
BEGIN
|
|
3938
|
-
IF normalized_queue_name
|
|
3939
|
-
OR left(normalized_queue_name, char_length('supacloud_internal_')) = 'supacloud_internal_' THEN
|
|
3941
|
+
IF left(normalized_queue_name, char_length('supacloud_internal_')) = 'supacloud_internal_' THEN
|
|
3940
3942
|
RAISE EXCEPTION 'SUPACLOUD_QUEUE_NAME_RESERVED' USING ERRCODE = '42501';
|
|
3941
3943
|
END IF;
|
|
3942
|
-
|
|
3944
|
+
IF queue_name IS NULL OR queue_name !~ '^[a-z0-9][a-z0-9_-]{0,127}$' THEN
|
|
3945
|
+
RAISE EXCEPTION 'SUPACLOUD_QUEUE_NAME_INVALID' USING ERRCODE = '22023';
|
|
3946
|
+
END IF;
|
|
3947
|
+
RETURN queue_name;
|
|
3948
|
+
END;
|
|
3949
|
+
$$;
|
|
3950
|
+
|
|
3951
|
+
-- Return-type changes require replacement. Do not CASCADE through user dependencies.
|
|
3952
|
+
DO $pgmq_receipt_types$
|
|
3953
|
+
BEGIN
|
|
3954
|
+
IF EXISTS (
|
|
3955
|
+
SELECT 1 FROM pg_catalog.pg_proc
|
|
3956
|
+
WHERE oid = to_regprocedure('pgmq_public.send(text,jsonb,integer)')
|
|
3957
|
+
AND prorettype = 'bigint'::regtype
|
|
3958
|
+
) THEN
|
|
3959
|
+
DROP FUNCTION pgmq_public.send(text,jsonb,integer);
|
|
3960
|
+
END IF;
|
|
3961
|
+
IF EXISTS (
|
|
3962
|
+
SELECT 1 FROM pg_catalog.pg_proc
|
|
3963
|
+
WHERE oid = to_regprocedure('pgmq_public.send_batch(text,jsonb[],integer)')
|
|
3964
|
+
AND prorettype = 'bigint'::regtype
|
|
3965
|
+
) THEN
|
|
3966
|
+
DROP FUNCTION pgmq_public.send_batch(text,jsonb[],integer);
|
|
3967
|
+
END IF;
|
|
3968
|
+
IF EXISTS (
|
|
3969
|
+
SELECT 1 FROM pg_catalog.pg_proc
|
|
3970
|
+
WHERE oid = to_regprocedure('pgmq_public.read(text,integer,integer)')
|
|
3971
|
+
AND prorettype = 'pgmq.message_record'::regtype
|
|
3972
|
+
) THEN
|
|
3973
|
+
DROP FUNCTION pgmq_public.read(text,integer,integer);
|
|
3974
|
+
END IF;
|
|
3975
|
+
IF EXISTS (
|
|
3976
|
+
SELECT 1 FROM pg_catalog.pg_proc
|
|
3977
|
+
WHERE oid = to_regprocedure('pgmq_public.pop(text)')
|
|
3978
|
+
AND prorettype = 'pgmq.message_record'::regtype
|
|
3979
|
+
) THEN
|
|
3980
|
+
DROP FUNCTION pgmq_public.pop(text);
|
|
3981
|
+
END IF;
|
|
3982
|
+
END
|
|
3983
|
+
$pgmq_receipt_types$;
|
|
3984
|
+
|
|
3985
|
+
CREATE OR REPLACE FUNCTION pgmq_public.require_seconds(value integer)
|
|
3986
|
+
RETURNS integer LANGUAGE plpgsql IMMUTABLE SET search_path = ''
|
|
3987
|
+
AS $$
|
|
3988
|
+
BEGIN
|
|
3989
|
+
IF value IS NULL OR value < 0 THEN
|
|
3990
|
+
RAISE EXCEPTION 'SUPACLOUD_QUEUE_SECONDS_INVALID' USING ERRCODE = '22023';
|
|
3991
|
+
END IF;
|
|
3992
|
+
RETURN value;
|
|
3993
|
+
END;
|
|
3994
|
+
$$;
|
|
3995
|
+
|
|
3996
|
+
CREATE OR REPLACE FUNCTION pgmq_public.require_read_count(value integer)
|
|
3997
|
+
RETURNS integer LANGUAGE plpgsql IMMUTABLE SET search_path = ''
|
|
3998
|
+
AS $$
|
|
3999
|
+
BEGIN
|
|
4000
|
+
IF value IS NULL OR value < 1 OR value > 10000 THEN
|
|
4001
|
+
RAISE EXCEPTION 'SUPACLOUD_QUEUE_COUNT_INVALID' USING ERRCODE = '22023';
|
|
4002
|
+
END IF;
|
|
4003
|
+
RETURN value;
|
|
4004
|
+
END;
|
|
4005
|
+
$$;
|
|
4006
|
+
|
|
4007
|
+
CREATE OR REPLACE FUNCTION pgmq_public.require_message_id(value bigint)
|
|
4008
|
+
RETURNS bigint LANGUAGE plpgsql IMMUTABLE SET search_path = ''
|
|
4009
|
+
AS $$
|
|
4010
|
+
BEGIN
|
|
4011
|
+
IF value IS NULL OR value < 1 THEN
|
|
4012
|
+
RAISE EXCEPTION 'SUPACLOUD_QUEUE_MESSAGE_ID_INVALID' USING ERRCODE = '22023';
|
|
4013
|
+
END IF;
|
|
4014
|
+
RETURN value;
|
|
4015
|
+
END;
|
|
4016
|
+
$$;
|
|
4017
|
+
|
|
4018
|
+
CREATE OR REPLACE FUNCTION pgmq_public.message_node_count(value jsonb)
|
|
4019
|
+
RETURNS integer LANGUAGE plpgsql IMMUTABLE SET search_path = ''
|
|
4020
|
+
AS $$
|
|
4021
|
+
DECLARE
|
|
4022
|
+
node_count integer;
|
|
4023
|
+
maximum_depth integer;
|
|
4024
|
+
BEGIN
|
|
4025
|
+
IF value IS NULL THEN
|
|
4026
|
+
RAISE EXCEPTION 'SUPACLOUD_QUEUE_MESSAGE_INVALID' USING ERRCODE = '22023';
|
|
4027
|
+
END IF;
|
|
4028
|
+
IF octet_length(convert_to(value::text, 'UTF8')) > 1048576 THEN
|
|
4029
|
+
RAISE EXCEPTION 'SUPACLOUD_QUEUE_PAYLOAD_TOO_LARGE' USING ERRCODE = '22023';
|
|
4030
|
+
END IF;
|
|
4031
|
+
WITH RECURSIVE tree(item, depth) AS (
|
|
4032
|
+
SELECT value, 0
|
|
4033
|
+
UNION ALL
|
|
4034
|
+
SELECT child.item, tree.depth + 1
|
|
4035
|
+
FROM tree
|
|
4036
|
+
CROSS JOIN LATERAL (
|
|
4037
|
+
SELECT element AS item
|
|
4038
|
+
FROM jsonb_array_elements(CASE WHEN jsonb_typeof(tree.item) = 'array' THEN tree.item ELSE '[]'::jsonb END) AS element
|
|
4039
|
+
UNION ALL
|
|
4040
|
+
SELECT entry.value AS item
|
|
4041
|
+
FROM jsonb_each(CASE WHEN jsonb_typeof(tree.item) = 'object' THEN tree.item ELSE '{}'::jsonb END) AS entry
|
|
4042
|
+
) AS child
|
|
4043
|
+
WHERE tree.depth < 65
|
|
4044
|
+
)
|
|
4045
|
+
SELECT count(*)::integer, max(depth) INTO node_count, maximum_depth
|
|
4046
|
+
FROM (SELECT depth FROM tree LIMIT 10001) AS bounded;
|
|
4047
|
+
IF node_count > 10000 OR maximum_depth > 64 THEN
|
|
4048
|
+
RAISE EXCEPTION 'SUPACLOUD_QUEUE_STRUCTURE_TOO_LARGE' USING ERRCODE = '22023';
|
|
4049
|
+
END IF;
|
|
4050
|
+
RETURN node_count;
|
|
4051
|
+
END;
|
|
4052
|
+
$$;
|
|
4053
|
+
|
|
4054
|
+
CREATE OR REPLACE FUNCTION pgmq_public.require_message(value jsonb)
|
|
4055
|
+
RETURNS jsonb LANGUAGE plpgsql IMMUTABLE SET search_path = ''
|
|
4056
|
+
AS $$
|
|
4057
|
+
BEGIN
|
|
4058
|
+
PERFORM pgmq_public.message_node_count(value);
|
|
4059
|
+
RETURN value;
|
|
4060
|
+
END;
|
|
4061
|
+
$$;
|
|
4062
|
+
|
|
4063
|
+
CREATE OR REPLACE FUNCTION pgmq_public.require_messages(values_ jsonb[])
|
|
4064
|
+
RETURNS jsonb[] LANGUAGE plpgsql IMMUTABLE SET search_path = ''
|
|
4065
|
+
AS $$
|
|
4066
|
+
DECLARE
|
|
4067
|
+
item jsonb;
|
|
4068
|
+
total_bytes bigint := 0;
|
|
4069
|
+
total_nodes integer := 0;
|
|
4070
|
+
BEGIN
|
|
4071
|
+
IF values_ IS NULL OR cardinality(values_) < 1 OR cardinality(values_) > 10000
|
|
4072
|
+
OR array_ndims(values_) <> 1 OR array_lower(values_, 1) <> 1 THEN
|
|
4073
|
+
RAISE EXCEPTION 'SUPACLOUD_QUEUE_BATCH_INVALID' USING ERRCODE = '22023';
|
|
4074
|
+
END IF;
|
|
4075
|
+
FOREACH item IN ARRAY values_ LOOP
|
|
4076
|
+
total_nodes := total_nodes + pgmq_public.message_node_count(item);
|
|
4077
|
+
IF total_nodes > 100000 THEN
|
|
4078
|
+
RAISE EXCEPTION 'SUPACLOUD_QUEUE_STRUCTURE_TOO_LARGE' USING ERRCODE = '22023';
|
|
4079
|
+
END IF;
|
|
4080
|
+
total_bytes := total_bytes + octet_length(convert_to(item::text, 'UTF8'));
|
|
4081
|
+
IF total_bytes > 8388608 THEN
|
|
4082
|
+
RAISE EXCEPTION 'SUPACLOUD_QUEUE_PAYLOAD_TOO_LARGE' USING ERRCODE = '22023';
|
|
4083
|
+
END IF;
|
|
4084
|
+
END LOOP;
|
|
4085
|
+
RETURN values_;
|
|
3943
4086
|
END;
|
|
3944
4087
|
$$;
|
|
3945
4088
|
|
|
3946
4089
|
CREATE OR REPLACE FUNCTION pgmq_public.send(queue_name text, message jsonb, sleep_seconds integer DEFAULT 0)
|
|
3947
|
-
RETURNS SETOF
|
|
4090
|
+
RETURNS SETOF text
|
|
3948
4091
|
LANGUAGE sql
|
|
3949
4092
|
VOLATILE
|
|
3950
4093
|
SECURITY DEFINER
|
|
3951
4094
|
SET search_path = ''
|
|
3952
|
-
AS $$ SELECT
|
|
4095
|
+
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); $$;
|
|
3953
4096
|
|
|
3954
4097
|
CREATE OR REPLACE FUNCTION pgmq_public.send_batch(queue_name text, messages jsonb[], sleep_seconds integer DEFAULT 0)
|
|
3955
|
-
RETURNS SETOF
|
|
4098
|
+
RETURNS SETOF text
|
|
3956
4099
|
LANGUAGE sql
|
|
3957
4100
|
VOLATILE
|
|
3958
4101
|
SECURITY DEFINER
|
|
3959
4102
|
SET search_path = ''
|
|
3960
|
-
AS $$ SELECT
|
|
4103
|
+
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); $$;
|
|
3961
4104
|
|
|
3962
4105
|
CREATE OR REPLACE FUNCTION pgmq_public.read(queue_name text, sleep_seconds integer, n integer)
|
|
3963
|
-
RETURNS
|
|
4106
|
+
RETURNS TABLE (
|
|
4107
|
+
msg_id text, read_ct integer, enqueued_at timestamptz,
|
|
4108
|
+
last_read_at timestamptz, vt timestamptz, message jsonb, headers jsonb
|
|
4109
|
+
)
|
|
3964
4110
|
LANGUAGE sql
|
|
3965
4111
|
VOLATILE
|
|
3966
4112
|
SECURITY DEFINER
|
|
3967
4113
|
SET search_path = ''
|
|
3968
|
-
AS $$
|
|
4114
|
+
AS $$
|
|
4115
|
+
SELECT receipt.msg_id::text, receipt.read_ct, receipt.enqueued_at,
|
|
4116
|
+
(to_jsonb(receipt)->>'last_read_at')::timestamptz, receipt.vt,
|
|
4117
|
+
receipt.message, to_jsonb(receipt)->'headers'
|
|
4118
|
+
FROM pgmq.read(pgmq_public.require_public_queue(queue_name),
|
|
4119
|
+
pgmq_public.require_seconds(sleep_seconds), pgmq_public.require_read_count(n)) AS receipt;
|
|
4120
|
+
$$;
|
|
3969
4121
|
|
|
3970
4122
|
CREATE OR REPLACE FUNCTION pgmq_public.pop(queue_name text)
|
|
3971
|
-
RETURNS
|
|
4123
|
+
RETURNS TABLE (
|
|
4124
|
+
msg_id text, read_ct integer, enqueued_at timestamptz,
|
|
4125
|
+
last_read_at timestamptz, vt timestamptz, message jsonb, headers jsonb
|
|
4126
|
+
)
|
|
3972
4127
|
LANGUAGE sql
|
|
3973
4128
|
VOLATILE
|
|
3974
4129
|
SECURITY DEFINER
|
|
3975
4130
|
SET search_path = ''
|
|
3976
|
-
AS $$
|
|
4131
|
+
AS $$
|
|
4132
|
+
SELECT receipt.msg_id::text, receipt.read_ct, receipt.enqueued_at,
|
|
4133
|
+
(to_jsonb(receipt)->>'last_read_at')::timestamptz, receipt.vt,
|
|
4134
|
+
receipt.message, to_jsonb(receipt)->'headers'
|
|
4135
|
+
FROM pgmq.pop(pgmq_public.require_public_queue(queue_name)) AS receipt;
|
|
4136
|
+
$$;
|
|
3977
4137
|
|
|
3978
4138
|
CREATE OR REPLACE FUNCTION pgmq_public.archive(queue_name text, message_id bigint)
|
|
3979
4139
|
RETURNS boolean
|
|
@@ -3981,7 +4141,7 @@ LANGUAGE sql
|
|
|
3981
4141
|
VOLATILE
|
|
3982
4142
|
SECURITY DEFINER
|
|
3983
4143
|
SET search_path = ''
|
|
3984
|
-
AS $$ SELECT pgmq.archive(pgmq_public.require_public_queue(queue_name), message_id); $$;
|
|
4144
|
+
AS $$ SELECT pgmq.archive(pgmq_public.require_public_queue(queue_name), pgmq_public.require_message_id(message_id)); $$;
|
|
3985
4145
|
|
|
3986
4146
|
CREATE OR REPLACE FUNCTION pgmq_public."delete"(queue_name text, message_id bigint)
|
|
3987
4147
|
RETURNS boolean
|
|
@@ -3989,10 +4149,12 @@ LANGUAGE sql
|
|
|
3989
4149
|
VOLATILE
|
|
3990
4150
|
SECURITY DEFINER
|
|
3991
4151
|
SET search_path = ''
|
|
3992
|
-
AS $$ SELECT pgmq.delete(pgmq_public.require_public_queue(queue_name), message_id); $$;
|
|
4152
|
+
AS $$ SELECT pgmq.delete(pgmq_public.require_public_queue(queue_name), pgmq_public.require_message_id(message_id)); $$;
|
|
3993
4153
|
|
|
3994
4154
|
REVOKE ALL ON ALL FUNCTIONS IN SCHEMA pgmq_public FROM PUBLIC;
|
|
3995
4155
|
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA pgmq_public TO anon, authenticated, service_role;
|
|
4156
|
+
|
|
4157
|
+
NOTIFY pgrst, 'reload schema';
|
|
3996
4158
|
-- supacloud:sql-module:pgmq-public:end
|
|
3997
4159
|
`;
|
|
3998
4160
|
var WORKFLOWS_SQL = `
|
|
@@ -4615,7 +4777,12 @@ BEGIN
|
|
|
4615
4777
|
OR (retry_receipt ->> 'delaySeconds')::integer IS DISTINCT FROM p_delay_seconds THEN
|
|
4616
4778
|
RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_IDEMPOTENCY_CONFLICT' USING ERRCODE = '23505';
|
|
4617
4779
|
END IF;
|
|
4618
|
-
|
|
4780
|
+
retry_receipt := retry_receipt || jsonb_build_object(
|
|
4781
|
+
'stepId', current_step.id,
|
|
4782
|
+
'attempt', p_attempt
|
|
4783
|
+
);
|
|
4784
|
+
RETURN supacloud_workflows.snapshot(current_step.run_id, true)
|
|
4785
|
+
|| jsonb_build_object('retryReceipt', retry_receipt);
|
|
4619
4786
|
END IF;
|
|
4620
4787
|
IF normalized_worker_id IS NULL
|
|
4621
4788
|
OR p_message_id IS NULL
|
|
@@ -4643,19 +4810,22 @@ BEGIN
|
|
|
4643
4810
|
IF NOT FOUND THEN
|
|
4644
4811
|
RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_RUN_NOT_ACTIVE' USING ERRCODE = '55000';
|
|
4645
4812
|
END IF;
|
|
4813
|
+
retry_receipt := jsonb_build_object(
|
|
4814
|
+
'operation', 'retry',
|
|
4815
|
+
'stepId', current_step.id,
|
|
4816
|
+
'messageId', p_message_id::text,
|
|
4817
|
+
'attempt', p_attempt,
|
|
4818
|
+
'workerId', normalized_worker_id,
|
|
4819
|
+
'errorMessage', normalized_error,
|
|
4820
|
+
'delaySeconds', p_delay_seconds
|
|
4821
|
+
);
|
|
4646
4822
|
INSERT INTO supacloud_workflows.events (
|
|
4647
4823
|
run_id, step_id, event_type, attempt, details
|
|
4648
4824
|
) VALUES (
|
|
4649
|
-
current_step.run_id, current_step.id, 'step_dead_lettered', p_attempt,
|
|
4650
|
-
jsonb_build_object(
|
|
4651
|
-
'operation', 'retry',
|
|
4652
|
-
'messageId', p_message_id::text,
|
|
4653
|
-
'workerId', normalized_worker_id,
|
|
4654
|
-
'errorMessage', normalized_error,
|
|
4655
|
-
'delaySeconds', p_delay_seconds
|
|
4656
|
-
)
|
|
4825
|
+
current_step.run_id, current_step.id, 'step_dead_lettered', p_attempt, retry_receipt
|
|
4657
4826
|
);
|
|
4658
|
-
RETURN supacloud_workflows.snapshot(current_step.run_id, false)
|
|
4827
|
+
RETURN supacloud_workflows.snapshot(current_step.run_id, false)
|
|
4828
|
+
|| jsonb_build_object('retryReceipt', retry_receipt);
|
|
4659
4829
|
END IF;
|
|
4660
4830
|
|
|
4661
4831
|
SELECT EXISTS (
|
|
@@ -4668,19 +4838,22 @@ BEGIN
|
|
|
4668
4838
|
SET status = 'queued', error_message = normalized_error,
|
|
4669
4839
|
retry_delay_seconds = p_delay_seconds, updated_at = now()
|
|
4670
4840
|
WHERE id = current_step.id;
|
|
4841
|
+
retry_receipt := jsonb_build_object(
|
|
4842
|
+
'operation', 'retry',
|
|
4843
|
+
'stepId', current_step.id,
|
|
4844
|
+
'messageId', p_message_id::text,
|
|
4845
|
+
'attempt', p_attempt,
|
|
4846
|
+
'workerId', normalized_worker_id,
|
|
4847
|
+
'errorMessage', normalized_error,
|
|
4848
|
+
'delaySeconds', p_delay_seconds
|
|
4849
|
+
);
|
|
4671
4850
|
INSERT INTO supacloud_workflows.events (
|
|
4672
4851
|
run_id, step_id, event_type, attempt, details
|
|
4673
4852
|
) VALUES (
|
|
4674
|
-
current_step.run_id, current_step.id, 'step_retried', p_attempt,
|
|
4675
|
-
jsonb_build_object(
|
|
4676
|
-
'operation', 'retry',
|
|
4677
|
-
'messageId', p_message_id::text,
|
|
4678
|
-
'workerId', normalized_worker_id,
|
|
4679
|
-
'errorMessage', normalized_error,
|
|
4680
|
-
'delaySeconds', p_delay_seconds
|
|
4681
|
-
)
|
|
4853
|
+
current_step.run_id, current_step.id, 'step_retried', p_attempt, retry_receipt
|
|
4682
4854
|
);
|
|
4683
|
-
RETURN supacloud_workflows.snapshot(current_step.run_id, false)
|
|
4855
|
+
RETURN supacloud_workflows.snapshot(current_step.run_id, false)
|
|
4856
|
+
|| jsonb_build_object('retryReceipt', retry_receipt);
|
|
4684
4857
|
END;
|
|
4685
4858
|
$$;
|
|
4686
4859
|
|
|
@@ -5091,6 +5264,8 @@ CREATE TABLE IF NOT EXISTS supacloud_commands.receipts (
|
|
|
5091
5264
|
CREATE INDEX IF NOT EXISTS supacloud_commands_target_idx
|
|
5092
5265
|
ON supacloud_commands.receipts (target_type, target_id, created_at DESC, id);
|
|
5093
5266
|
|
|
5267
|
+
ALTER TABLE supacloud_commands.receipts ADD COLUMN IF NOT EXISTS tenant_id text;
|
|
5268
|
+
|
|
5094
5269
|
CREATE OR REPLACE FUNCTION supacloud_commands.snapshot(
|
|
5095
5270
|
p_command_id uuid,
|
|
5096
5271
|
p_idempotent boolean DEFAULT false
|
|
@@ -5102,6 +5277,7 @@ LANGUAGE sql STABLE SECURITY DEFINER SET search_path = '' AS $$
|
|
|
5102
5277
|
'targetType', receipt.target_type,
|
|
5103
5278
|
'targetId', receipt.target_id,
|
|
5104
5279
|
'actorId', receipt.actor_id,
|
|
5280
|
+
'tenantId', receipt.tenant_id,
|
|
5105
5281
|
'payloadFingerprint', receipt.payload_fingerprint,
|
|
5106
5282
|
'createdAt', receipt.created_at,
|
|
5107
5283
|
'idempotent', p_idempotent,
|
|
@@ -5129,6 +5305,8 @@ DECLARE
|
|
|
5129
5305
|
normalized_target_id text;
|
|
5130
5306
|
fingerprint text;
|
|
5131
5307
|
existing supacloud_commands.receipts%ROWTYPE;
|
|
5308
|
+
tenant_id text;
|
|
5309
|
+
existing_execution jsonb;
|
|
5132
5310
|
BEGIN
|
|
5133
5311
|
IF jsonb_typeof(request) IS DISTINCT FROM 'object' THEN
|
|
5134
5312
|
RAISE EXCEPTION 'SUPACLOUD_COMMAND_INVALID' USING ERRCODE = '22023';
|
|
@@ -5139,6 +5317,11 @@ BEGIN
|
|
|
5139
5317
|
RAISE EXCEPTION 'SUPACLOUD_COMMAND_INVALID' USING ERRCODE = '22023';
|
|
5140
5318
|
END IF;
|
|
5141
5319
|
command_id := command_id_text::uuid;
|
|
5320
|
+
tenant_id := request->>'tenantId';
|
|
5321
|
+
IF request ? 'tenantId' AND (jsonb_typeof(request->'tenantId') IS DISTINCT FROM 'string'
|
|
5322
|
+
OR tenant_id !~ '^[A-Za-z0-9._:@|-]{1,200}$') THEN
|
|
5323
|
+
RAISE EXCEPTION 'SUPACLOUD_COMMAND_INVALID' USING ERRCODE = '22023';
|
|
5324
|
+
END IF;
|
|
5142
5325
|
IF request ? 'actorId' AND request ->> 'actorId' IS NOT NULL THEN
|
|
5143
5326
|
actor_id := (request ->> 'actorId')::uuid;
|
|
5144
5327
|
END IF;
|
|
@@ -5166,12 +5349,20 @@ BEGIN
|
|
|
5166
5349
|
OR existing.target_type <> normalized_target_type
|
|
5167
5350
|
OR existing.target_id <> normalized_target_id
|
|
5168
5351
|
OR existing.actor_id IS DISTINCT FROM actor_id
|
|
5352
|
+
OR existing.tenant_id IS DISTINCT FROM tenant_id
|
|
5169
5353
|
OR existing.payload <> payload
|
|
5170
5354
|
OR existing.payload_fingerprint <> fingerprint THEN
|
|
5171
5355
|
RAISE EXCEPTION 'SUPACLOUD_COMMAND_IDEMPOTENCY_CONFLICT' USING ERRCODE = '23505';
|
|
5172
5356
|
END IF;
|
|
5173
5357
|
RETURN supacloud_commands.snapshot(command_id, true);
|
|
5174
5358
|
END IF;
|
|
5359
|
+
IF to_regprocedure('supacloud_commands.execution_status(jsonb)') IS NOT NULL THEN
|
|
5360
|
+
EXECUTE 'SELECT supacloud_commands.execution_status($1)' INTO existing_execution
|
|
5361
|
+
USING jsonb_build_object('commandId',command_id);
|
|
5362
|
+
IF existing_execution IS NOT NULL THEN
|
|
5363
|
+
RAISE EXCEPTION 'SUPACLOUD_COMMAND_IDEMPOTENCY_CONFLICT' USING ERRCODE = '23505';
|
|
5364
|
+
END IF;
|
|
5365
|
+
END IF;
|
|
5175
5366
|
|
|
5176
5367
|
PERFORM supacloud_workflows.start_run(
|
|
5177
5368
|
command_id,
|
|
@@ -5192,10 +5383,10 @@ BEGIN
|
|
|
5192
5383
|
|
|
5193
5384
|
INSERT INTO supacloud_commands.receipts (
|
|
5194
5385
|
id, command_type, target_type, target_id, actor_id, payload,
|
|
5195
|
-
payload_fingerprint, workflow_run_id
|
|
5386
|
+
payload_fingerprint, workflow_run_id, tenant_id
|
|
5196
5387
|
) VALUES (
|
|
5197
5388
|
command_id, normalized_command_type, normalized_target_type,
|
|
5198
|
-
normalized_target_id, actor_id, payload, fingerprint, command_id
|
|
5389
|
+
normalized_target_id, actor_id, payload, fingerprint, command_id, tenant_id
|
|
5199
5390
|
);
|
|
5200
5391
|
RETURN supacloud_commands.snapshot(command_id, false);
|
|
5201
5392
|
EXCEPTION
|
|
@@ -5204,33 +5395,79 @@ EXCEPTION
|
|
|
5204
5395
|
END;
|
|
5205
5396
|
$$;
|
|
5206
5397
|
|
|
5207
|
-
CREATE OR REPLACE FUNCTION
|
|
5208
|
-
RETURNS jsonb
|
|
5209
|
-
LANGUAGE sql VOLATILE SECURITY DEFINER SET search_path = '' AS $$
|
|
5210
|
-
SELECT supacloud_commands.submit(request)
|
|
5211
|
-
$$;
|
|
5212
|
-
|
|
5213
|
-
CREATE OR REPLACE FUNCTION public.supacloud_command_get(request jsonb)
|
|
5398
|
+
CREATE OR REPLACE FUNCTION supacloud_commands.status(request jsonb)
|
|
5214
5399
|
RETURNS jsonb
|
|
5215
5400
|
LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = '' AS $$
|
|
5216
5401
|
DECLARE
|
|
5217
5402
|
command_id_text text;
|
|
5403
|
+
execution jsonb;
|
|
5404
|
+
submission jsonb;
|
|
5405
|
+
field text;
|
|
5218
5406
|
BEGIN
|
|
5219
5407
|
IF jsonb_typeof(request) IS DISTINCT FROM 'object' THEN
|
|
5220
5408
|
RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';
|
|
5221
5409
|
END IF;
|
|
5410
|
+
IF request ? 'commandId' THEN
|
|
5411
|
+
IF jsonb_typeof(request->'commandId') IS DISTINCT FROM 'string'
|
|
5412
|
+
OR request->>'commandId' !~* '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
|
|
5413
|
+
OR EXISTS(SELECT FROM jsonb_object_keys(request) AS k WHERE k <> 'commandId') THEN
|
|
5414
|
+
RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';
|
|
5415
|
+
END IF;
|
|
5416
|
+
request := jsonb_build_object('commandId',(request->>'commandId')::uuid::text);
|
|
5417
|
+
ELSE
|
|
5418
|
+
IF EXISTS(SELECT FROM jsonb_object_keys(request) AS k WHERE k NOT IN ('tenantId','actorId','command','operationId')) THEN
|
|
5419
|
+
RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';
|
|
5420
|
+
END IF;
|
|
5421
|
+
FOREACH field IN ARRAY ARRAY['tenantId','actorId','command','operationId'] LOOP
|
|
5422
|
+
IF jsonb_typeof(request->field) IS DISTINCT FROM 'string' OR request->>field !~ '^[A-Za-z0-9._:@|-]{1,200}$' THEN
|
|
5423
|
+
RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';
|
|
5424
|
+
END IF;
|
|
5425
|
+
END LOOP;
|
|
5426
|
+
END IF;
|
|
5427
|
+
IF to_regprocedure('supacloud_commands.execution_status(jsonb)') IS NOT NULL THEN
|
|
5428
|
+
EXECUTE 'SELECT supacloud_commands.execution_status($1)' INTO execution USING request;
|
|
5429
|
+
IF execution IS NOT NULL THEN RETURN execution; END IF;
|
|
5430
|
+
END IF;
|
|
5431
|
+
IF NOT (request ? 'commandId') THEN
|
|
5432
|
+
IF jsonb_typeof(request->'tenantId') IS DISTINCT FROM 'string'
|
|
5433
|
+
OR jsonb_typeof(request->'actorId') IS DISTINCT FROM 'string'
|
|
5434
|
+
OR jsonb_typeof(request->'command') IS DISTINCT FROM 'string'
|
|
5435
|
+
OR jsonb_typeof(request->'operationId') IS DISTINCT FROM 'string' THEN
|
|
5436
|
+
RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';
|
|
5437
|
+
END IF;
|
|
5438
|
+
RETURN NULL;
|
|
5439
|
+
END IF;
|
|
5222
5440
|
command_id_text := request ->> 'commandId';
|
|
5223
5441
|
IF command_id_text IS NULL
|
|
5224
5442
|
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
|
|
5225
5443
|
RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';
|
|
5226
5444
|
END IF;
|
|
5227
|
-
|
|
5445
|
+
submission := supacloud_commands.snapshot(command_id_text::uuid, false);
|
|
5446
|
+
IF submission IS NULL THEN RETURN NULL; END IF;
|
|
5447
|
+
RETURN jsonb_build_object('kind','submission','commandId',command_id_text::uuid,
|
|
5448
|
+
'execution',NULL,'workflow',jsonb_build_object(
|
|
5449
|
+
'runId',submission->'workflow'->'runId','status',submission->'workflow'->'status'));
|
|
5228
5450
|
EXCEPTION
|
|
5229
5451
|
WHEN invalid_parameter_value OR invalid_text_representation THEN
|
|
5230
5452
|
RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';
|
|
5231
5453
|
END;
|
|
5232
5454
|
$$;
|
|
5233
5455
|
|
|
5456
|
+
CREATE OR REPLACE FUNCTION public.supacloud_command_submit(request jsonb)
|
|
5457
|
+
RETURNS jsonb
|
|
5458
|
+
LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path = '' AS $$
|
|
5459
|
+
BEGIN
|
|
5460
|
+
PERFORM supacloud_commands.submit(request);
|
|
5461
|
+
RETURN supacloud_commands.status(jsonb_build_object('commandId',request->>'commandId'));
|
|
5462
|
+
END
|
|
5463
|
+
$$;
|
|
5464
|
+
|
|
5465
|
+
CREATE OR REPLACE FUNCTION public.supacloud_command_get(request jsonb)
|
|
5466
|
+
RETURNS jsonb
|
|
5467
|
+
LANGUAGE sql STABLE SECURITY DEFINER SET search_path = '' AS $$
|
|
5468
|
+
SELECT supacloud_commands.status(request)
|
|
5469
|
+
$$;
|
|
5470
|
+
|
|
5234
5471
|
REVOKE ALL ON ALL TABLES IN SCHEMA supacloud_commands
|
|
5235
5472
|
FROM PUBLIC, anon, authenticated, service_role;
|
|
5236
5473
|
REVOKE ALL ON ALL FUNCTIONS IN SCHEMA supacloud_commands
|
|
@@ -6886,9 +7123,9 @@ class RealtimeEngine {
|
|
|
6886
7123
|
return;
|
|
6887
7124
|
const [, joinRefLen, refLen, topicLen, eventLen, metaLen, encoding] = bytes;
|
|
6888
7125
|
let offset = 7;
|
|
6889
|
-
const
|
|
7126
|
+
const decoder = new TextDecoder;
|
|
6890
7127
|
const read = (len) => {
|
|
6891
|
-
const out =
|
|
7128
|
+
const out = decoder.decode(bytes.subarray(offset, offset + len));
|
|
6892
7129
|
offset += len;
|
|
6893
7130
|
return out;
|
|
6894
7131
|
};
|
|
@@ -6901,7 +7138,7 @@ class RealtimeEngine {
|
|
|
6901
7138
|
const payload = {
|
|
6902
7139
|
type: "broadcast",
|
|
6903
7140
|
event: userEvent,
|
|
6904
|
-
payload: encoding === 1 ? safeJsonParse(
|
|
7141
|
+
payload: encoding === 1 ? safeJsonParse(decoder.decode(payloadBytes)) : payloadBytes
|
|
6905
7142
|
};
|
|
6906
7143
|
this.handleBroadcast(conn, {
|
|
6907
7144
|
topic,
|
|
@@ -7212,24 +7449,24 @@ function parseQuery(searchParams) {
|
|
|
7212
7449
|
const segments = key.split(".");
|
|
7213
7450
|
const last = segments[segments.length - 1];
|
|
7214
7451
|
if (last === "order") {
|
|
7215
|
-
const
|
|
7452
|
+
const path = segments.slice(0, -1);
|
|
7216
7453
|
for (const term of splitTopLevel(value, ",")) {
|
|
7217
|
-
q.order.push(parseOrderTerm(term.trim(),
|
|
7454
|
+
q.order.push(parseOrderTerm(term.trim(), path));
|
|
7218
7455
|
}
|
|
7219
7456
|
continue;
|
|
7220
7457
|
}
|
|
7221
7458
|
if (last === "limit" || last === "offset") {
|
|
7222
|
-
const
|
|
7459
|
+
const path = segments.slice(0, -1).join(".");
|
|
7223
7460
|
const n = parseInt(value, 10);
|
|
7224
7461
|
if (Number.isNaN(n) || n < 0)
|
|
7225
7462
|
throw new ParseError(`invalid ${last}: ${value}`);
|
|
7226
|
-
(last === "limit" ? q.limits : q.offsets).set(
|
|
7463
|
+
(last === "limit" ? q.limits : q.offsets).set(path, n);
|
|
7227
7464
|
continue;
|
|
7228
7465
|
}
|
|
7229
7466
|
if (last === "or" || last === "and") {
|
|
7230
7467
|
const negated = segments[segments.length - 2] === "not";
|
|
7231
|
-
const
|
|
7232
|
-
q.conditions.push(parseLogicTree(last, value,
|
|
7468
|
+
const path = segments.slice(0, negated ? -2 : -1);
|
|
7469
|
+
q.conditions.push(parseLogicTree(last, value, path, negated));
|
|
7233
7470
|
continue;
|
|
7234
7471
|
}
|
|
7235
7472
|
if (RESERVED.has(key))
|
|
@@ -7669,13 +7906,13 @@ class QueryBuilder {
|
|
|
7669
7906
|
if (c.kind !== "column" || c.name === "*") {
|
|
7670
7907
|
throw new ParseError("spread embeds support explicit columns only");
|
|
7671
7908
|
}
|
|
7672
|
-
const
|
|
7909
|
+
const expr = renderColumnExpr(childAlias, c.name);
|
|
7673
7910
|
const cast = c.cast ? `::${sanitizeCast(c.cast)}` : "";
|
|
7674
7911
|
const out = c.alias ?? defaultAliasFor(c.name);
|
|
7675
|
-
return toOne ? `(select ${
|
|
7912
|
+
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)}`;
|
|
7676
7913
|
});
|
|
7677
|
-
const
|
|
7678
|
-
return { exprs, innerCond
|
|
7914
|
+
const innerCond = embed.inner ? `exists (select 1 from ${fromClause}${where})` : undefined;
|
|
7915
|
+
return { exprs, innerCond };
|
|
7679
7916
|
}
|
|
7680
7917
|
const sub = `select ${childExprs.join(", ")} from ${fromClause}${where}${order}${limitOffset}`;
|
|
7681
7918
|
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)}`;
|
|
@@ -8134,12 +8371,12 @@ class RestHandler {
|
|
|
8134
8371
|
const built = builder.buildSelect(table, { count: prefer.count !== undefined });
|
|
8135
8372
|
const { rows, count } = await this.db.withContext(ctx, async (query) => {
|
|
8136
8373
|
const res = await query(built.sql, built.params);
|
|
8137
|
-
let
|
|
8374
|
+
let count = null;
|
|
8138
8375
|
if (built.countSql) {
|
|
8139
8376
|
const c = await query(built.countSql, []);
|
|
8140
|
-
|
|
8377
|
+
count = c.rows[0].count;
|
|
8141
8378
|
}
|
|
8142
|
-
return { rows: res.rows[0].body, count
|
|
8379
|
+
return { rows: res.rows[0].body, count };
|
|
8143
8380
|
});
|
|
8144
8381
|
const offset = q.offsets.get("") ?? 0;
|
|
8145
8382
|
return this.dataResponse(rows, {
|
|
@@ -8335,12 +8572,12 @@ class RestHandler {
|
|
|
8335
8572
|
const countSql = prefer.count !== undefined ? `select count(*)::int as count from ${call} as ${quoteIdent(alias)}${where}` : undefined;
|
|
8336
8573
|
const { rows, count } = await this.db.withContext(ctx, async (query) => {
|
|
8337
8574
|
const res = await query(sql, []);
|
|
8338
|
-
let
|
|
8575
|
+
let count = null;
|
|
8339
8576
|
if (countSql) {
|
|
8340
8577
|
const c = await query(countSql, []);
|
|
8341
|
-
|
|
8578
|
+
count = c.rows[0].count;
|
|
8342
8579
|
}
|
|
8343
|
-
return { rows: res.rows[0].body, count
|
|
8580
|
+
return { rows: res.rows[0].body, count };
|
|
8344
8581
|
});
|
|
8345
8582
|
return this.dataResponse(rows, {
|
|
8346
8583
|
status: 200,
|
|
@@ -8656,7 +8893,7 @@ class Semaphore {
|
|
|
8656
8893
|
this.active += 1;
|
|
8657
8894
|
return;
|
|
8658
8895
|
}
|
|
8659
|
-
await new Promise((
|
|
8896
|
+
await new Promise((resolve) => this.waiters.push(resolve));
|
|
8660
8897
|
}
|
|
8661
8898
|
release() {
|
|
8662
8899
|
const next = this.waiters.shift();
|
|
@@ -8862,22 +9099,22 @@ class StorageHandler {
|
|
|
8862
9099
|
}
|
|
8863
9100
|
if (parts[0] === "render" && parts[1] === "image" && parts.length >= 5) {
|
|
8864
9101
|
const kind = parts[2];
|
|
8865
|
-
const
|
|
8866
|
-
const
|
|
9102
|
+
const bucket = parts[3];
|
|
9103
|
+
const key = parts.slice(4).join("/");
|
|
8867
9104
|
if (kind === "public" && (method === "GET" || method === "HEAD")) {
|
|
8868
|
-
const source = await this.loadPublicObject(
|
|
9105
|
+
const source = await this.loadPublicObject(bucket, key);
|
|
8869
9106
|
if (source instanceof Response)
|
|
8870
9107
|
return source;
|
|
8871
9108
|
return await this.transformImageResponse(source, url, method === "HEAD");
|
|
8872
9109
|
}
|
|
8873
9110
|
if (kind === "authenticated" && (method === "GET" || method === "HEAD")) {
|
|
8874
|
-
const source = await this.loadAuthenticatedObject(ctx,
|
|
9111
|
+
const source = await this.loadAuthenticatedObject(ctx, bucket, key);
|
|
8875
9112
|
if (source instanceof Response)
|
|
8876
9113
|
return source;
|
|
8877
9114
|
return await this.transformImageResponse(source, url, method === "HEAD");
|
|
8878
9115
|
}
|
|
8879
9116
|
if (kind === "sign" && method === "GET") {
|
|
8880
|
-
const source = await this.loadSignedObject(url,
|
|
9117
|
+
const source = await this.loadSignedObject(url, bucket, key);
|
|
8881
9118
|
if (source instanceof Response)
|
|
8882
9119
|
return source;
|
|
8883
9120
|
return await this.transformImageResponse(source, url, false);
|
|
@@ -8911,12 +9148,12 @@ class StorageHandler {
|
|
|
8911
9148
|
return await this.redeemSignedUrl(url, parts[2], parts.slice(3).join("/"));
|
|
8912
9149
|
}
|
|
8913
9150
|
if (parts[1] === "upload" && parts[2] === "sign" && parts.length >= 5) {
|
|
8914
|
-
const
|
|
8915
|
-
const
|
|
9151
|
+
const bucket = parts[3];
|
|
9152
|
+
const key = parts.slice(4).join("/");
|
|
8916
9153
|
if (method === "POST")
|
|
8917
|
-
return await this.signUploadUrl(ctx,
|
|
9154
|
+
return await this.signUploadUrl(ctx, bucket, key);
|
|
8918
9155
|
if (method === "PUT")
|
|
8919
|
-
return await this.redeemSignedUpload(req, url,
|
|
9156
|
+
return await this.redeemSignedUpload(req, url, bucket, key);
|
|
8920
9157
|
}
|
|
8921
9158
|
if (parts[1] === "info" && parts.length >= 4 && method === "GET") {
|
|
8922
9159
|
return await this.objectInfo(ctx, parts[2], parts.slice(3).join("/"));
|
|
@@ -8951,8 +9188,8 @@ class StorageHandler {
|
|
|
8951
9188
|
async withMutation(operation) {
|
|
8952
9189
|
const previous = this.mutationTail;
|
|
8953
9190
|
let release;
|
|
8954
|
-
this.mutationTail = new Promise((
|
|
8955
|
-
release =
|
|
9191
|
+
this.mutationTail = new Promise((resolve) => {
|
|
9192
|
+
release = resolve;
|
|
8956
9193
|
});
|
|
8957
9194
|
await previous;
|
|
8958
9195
|
try {
|
|
@@ -9204,8 +9441,8 @@ class StorageHandler {
|
|
|
9204
9441
|
return storageError(409, "Duplicate", "The resource already exists");
|
|
9205
9442
|
throw error;
|
|
9206
9443
|
}
|
|
9207
|
-
const
|
|
9208
|
-
this.tusUploads.set(
|
|
9444
|
+
const uploadId = crypto.randomUUID();
|
|
9445
|
+
this.tusUploads.set(uploadId, {
|
|
9209
9446
|
bucketId,
|
|
9210
9447
|
key,
|
|
9211
9448
|
contentType,
|
|
@@ -9221,13 +9458,13 @@ class StorageHandler {
|
|
|
9221
9458
|
return new Response(null, {
|
|
9222
9459
|
status: 201,
|
|
9223
9460
|
headers: tus({
|
|
9224
|
-
location: `${url.origin}/storage/v1/upload/resumable/${
|
|
9461
|
+
location: `${url.origin}/storage/v1/upload/resumable/${uploadId}`,
|
|
9225
9462
|
"upload-offset": "0",
|
|
9226
9463
|
"upload-length": "0"
|
|
9227
9464
|
})
|
|
9228
9465
|
});
|
|
9229
9466
|
}
|
|
9230
|
-
const activeUploads = [...this.tusUploads.values()].filter((
|
|
9467
|
+
const activeUploads = [...this.tusUploads.values()].filter((upload) => upload.completedAt === undefined).length;
|
|
9231
9468
|
if (activeUploads >= MAX_CONCURRENT_TUS_UPLOADS) {
|
|
9232
9469
|
return storageError(429, "too_many_requests", "Too many resumable uploads are active");
|
|
9233
9470
|
}
|
|
@@ -9235,7 +9472,7 @@ class StorageHandler {
|
|
|
9235
9472
|
if (preflightError)
|
|
9236
9473
|
return preflightError;
|
|
9237
9474
|
const uploadId = crypto.randomUUID();
|
|
9238
|
-
const
|
|
9475
|
+
const upload = {
|
|
9239
9476
|
bucketId,
|
|
9240
9477
|
key,
|
|
9241
9478
|
contentType,
|
|
@@ -9246,7 +9483,7 @@ class StorageHandler {
|
|
|
9246
9483
|
chunks: [],
|
|
9247
9484
|
ctx
|
|
9248
9485
|
};
|
|
9249
|
-
this.tusUploads.set(uploadId,
|
|
9486
|
+
this.tusUploads.set(uploadId, upload);
|
|
9250
9487
|
if ((req.headers.get("content-type") ?? "").includes("application/offset+octet-stream")) {
|
|
9251
9488
|
const chunk = await readLimitedBody(req, length);
|
|
9252
9489
|
if (chunk === null) {
|
|
@@ -9254,7 +9491,7 @@ class StorageHandler {
|
|
|
9254
9491
|
return storageError(400, "invalid_request", "chunk exceeds the declared Upload-Length");
|
|
9255
9492
|
}
|
|
9256
9493
|
if (chunk.length > 0) {
|
|
9257
|
-
const err = await this.appendTus(uploadId,
|
|
9494
|
+
const err = await this.appendTus(uploadId, upload, 0, chunk);
|
|
9258
9495
|
if (err)
|
|
9259
9496
|
return err;
|
|
9260
9497
|
}
|
|
@@ -9263,7 +9500,7 @@ class StorageHandler {
|
|
|
9263
9500
|
status: 201,
|
|
9264
9501
|
headers: tus({
|
|
9265
9502
|
location: `${url.origin}/storage/v1/upload/resumable/${uploadId}`,
|
|
9266
|
-
"upload-offset": String(
|
|
9503
|
+
"upload-offset": String(upload.offset)
|
|
9267
9504
|
})
|
|
9268
9505
|
});
|
|
9269
9506
|
}
|
|
@@ -11068,8 +11305,8 @@ ${msg.text}` : `[mail] to=${msg.to} subject="${msg.subject}"`));
|
|
|
11068
11305
|
headers: { "content-type": "application/json" }
|
|
11069
11306
|
}));
|
|
11070
11307
|
}
|
|
11071
|
-
const
|
|
11072
|
-
return withCors(await auth.handle(req,
|
|
11308
|
+
const ctx = { role: String(keyClaims.role ?? "anon"), claims: keyClaims };
|
|
11309
|
+
return withCors(await auth.handle(req, ctx, url));
|
|
11073
11310
|
}
|
|
11074
11311
|
if (path.startsWith("/functions/v1/")) {
|
|
11075
11312
|
const functionName = path.replace(/^\/functions\/v1\/?/, "").split("/")[0];
|
|
@@ -11459,9 +11696,9 @@ function validatePublicationTables(tables) {
|
|
|
11459
11696
|
if (missing.length > 0) {
|
|
11460
11697
|
throw new Error(`PowerSync publication tables do not exist: ${missing.join(", ")}`);
|
|
11461
11698
|
}
|
|
11462
|
-
const
|
|
11463
|
-
if (
|
|
11464
|
-
throw new Error(`PowerSync publication only accepts ordinary or partitioned tables: ${
|
|
11699
|
+
const unsupported = tables.filter((table) => table.present && !["r", "p"].includes(table.relkind ?? ""));
|
|
11700
|
+
if (unsupported.length > 0) {
|
|
11701
|
+
throw new Error(`PowerSync publication only accepts ordinary or partitioned tables: ${unsupported.map((table) => table.name).join(", ")}`);
|
|
11465
11702
|
}
|
|
11466
11703
|
}
|
|
11467
11704
|
async function configureReplicationRole(tx, passwordValue) {
|
|
@@ -11669,7 +11906,7 @@ class PgWireClient {
|
|
|
11669
11906
|
return client;
|
|
11670
11907
|
}
|
|
11671
11908
|
open(opts) {
|
|
11672
|
-
return new Promise((
|
|
11909
|
+
return new Promise((resolve, reject) => {
|
|
11673
11910
|
let startupComplete = false;
|
|
11674
11911
|
this.socket = opts.socketPath ? createConnection(opts.socketPath) : createConnection(opts.port ?? 5432, opts.host ?? "127.0.0.1");
|
|
11675
11912
|
this.socket.setTimeout(1e4, () => this.socket.destroy(new Error("PostgreSQL handshake timed out")));
|
|
@@ -11773,7 +12010,7 @@ class PgWireClient {
|
|
|
11773
12010
|
this.buffer = Buffer.concat([this.buffer, c]);
|
|
11774
12011
|
this.processMessages();
|
|
11775
12012
|
});
|
|
11776
|
-
|
|
12013
|
+
resolve();
|
|
11777
12014
|
return;
|
|
11778
12015
|
}
|
|
11779
12016
|
}
|
|
@@ -11782,10 +12019,10 @@ class PgWireClient {
|
|
|
11782
12019
|
});
|
|
11783
12020
|
}
|
|
11784
12021
|
run(send) {
|
|
11785
|
-
const op = this.queue.then(() => new Promise((
|
|
12022
|
+
const op = this.queue.then(() => new Promise((resolve, reject) => {
|
|
11786
12023
|
if (this.closed)
|
|
11787
12024
|
return reject(new Error("connection closed"));
|
|
11788
|
-
this.pending = { resolve
|
|
12025
|
+
this.pending = { resolve, reject, results: [], columns: [], error: null };
|
|
11789
12026
|
send();
|
|
11790
12027
|
}));
|
|
11791
12028
|
this.queue = op.catch(() => {});
|
|
@@ -11816,11 +12053,11 @@ class PgWireClient {
|
|
|
11816
12053
|
return results[0] ?? { rows: [] };
|
|
11817
12054
|
}
|
|
11818
12055
|
close() {
|
|
11819
|
-
return new Promise((
|
|
12056
|
+
return new Promise((resolve) => {
|
|
11820
12057
|
if (this.closed)
|
|
11821
|
-
return
|
|
12058
|
+
return resolve();
|
|
11822
12059
|
this.socket.write(message(88, Buffer.alloc(0)));
|
|
11823
|
-
this.socket.end(() =>
|
|
12060
|
+
this.socket.end(() => resolve());
|
|
11824
12061
|
});
|
|
11825
12062
|
}
|
|
11826
12063
|
nextMessage() {
|
|
@@ -11991,10 +12228,10 @@ function decodeValue(text, oid) {
|
|
|
11991
12228
|
case 1114:
|
|
11992
12229
|
return new Date(text.replace(" ", "T") + "Z");
|
|
11993
12230
|
case 1184: {
|
|
11994
|
-
let
|
|
11995
|
-
if (/[+-]\d\d$/.test(
|
|
11996
|
-
|
|
11997
|
-
return new Date(
|
|
12231
|
+
let iso = text.replace(" ", "T");
|
|
12232
|
+
if (/[+-]\d\d$/.test(iso))
|
|
12233
|
+
iso += ":00";
|
|
12234
|
+
return new Date(iso);
|
|
11998
12235
|
}
|
|
11999
12236
|
case 1000:
|
|
12000
12237
|
return parsePgArray(text).map((v) => v === "t");
|
|
@@ -12083,9 +12320,9 @@ async function buildWireEngine(options) {
|
|
|
12083
12320
|
return queryMutex.run(async () => {
|
|
12084
12321
|
await queryClient.exec("begin");
|
|
12085
12322
|
try {
|
|
12086
|
-
const
|
|
12323
|
+
const response = await callback(transactionClient);
|
|
12087
12324
|
await queryClient.exec("commit");
|
|
12088
|
-
return
|
|
12325
|
+
return response;
|
|
12089
12326
|
} catch (error) {
|
|
12090
12327
|
await queryClient.exec("rollback").catch(() => {});
|
|
12091
12328
|
throw error;
|
|
@@ -12314,15 +12551,15 @@ async function fetchRelease(url) {
|
|
|
12314
12551
|
let lastError;
|
|
12315
12552
|
for (let attempt = 1;attempt <= 3; attempt++) {
|
|
12316
12553
|
try {
|
|
12317
|
-
const
|
|
12318
|
-
if (
|
|
12319
|
-
return
|
|
12320
|
-
lastError = new Error(`failed to download ${url}: HTTP ${
|
|
12554
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(30000) });
|
|
12555
|
+
if (response.ok || response.status < 500)
|
|
12556
|
+
return response;
|
|
12557
|
+
lastError = new Error(`failed to download ${url}: HTTP ${response.status}`);
|
|
12321
12558
|
} catch (error) {
|
|
12322
12559
|
lastError = error;
|
|
12323
12560
|
}
|
|
12324
12561
|
if (attempt < 3)
|
|
12325
|
-
await new Promise((
|
|
12562
|
+
await new Promise((resolve) => setTimeout(resolve, attempt * 500));
|
|
12326
12563
|
}
|
|
12327
12564
|
throw lastError instanceof Error ? lastError : new Error(`failed to download ${url}`);
|
|
12328
12565
|
}
|
|
@@ -12414,7 +12651,7 @@ ${detail}` : " (no output)"}
|
|
|
12414
12651
|
` + `data dir: ${opts.dataDir}
|
|
12415
12652
|
` + "If a previous run is still holding it, stop it; or delete the data dir to start fresh.");
|
|
12416
12653
|
}
|
|
12417
|
-
await new Promise((
|
|
12654
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
12418
12655
|
}
|
|
12419
12656
|
}
|
|
12420
12657
|
throw new Error(`timed out waiting for embedded postgres at ${socketPath}`);
|
|
@@ -12442,14 +12679,14 @@ async function stopPostgres(postgres, hasExited) {
|
|
|
12442
12679
|
if (hasExited())
|
|
12443
12680
|
return;
|
|
12444
12681
|
postgres.kill("SIGINT");
|
|
12445
|
-
await new Promise((
|
|
12682
|
+
await new Promise((resolve) => {
|
|
12446
12683
|
const killTimeout = setTimeout(() => {
|
|
12447
12684
|
postgres.kill("SIGKILL");
|
|
12448
|
-
|
|
12685
|
+
resolve();
|
|
12449
12686
|
}, 5000);
|
|
12450
12687
|
postgres.once("exit", () => {
|
|
12451
12688
|
clearTimeout(killTimeout);
|
|
12452
|
-
|
|
12689
|
+
resolve();
|
|
12453
12690
|
});
|
|
12454
12691
|
});
|
|
12455
12692
|
}
|
|
@@ -12831,8 +13068,8 @@ function getDurationSeconds(table, key) {
|
|
|
12831
13068
|
return;
|
|
12832
13069
|
const m = v.trim().match(/^(\d+)\s*(s|m|h)$/i);
|
|
12833
13070
|
if (!m) {
|
|
12834
|
-
const
|
|
12835
|
-
return Number.isFinite(
|
|
13071
|
+
const n = parseInt(v, 10);
|
|
13072
|
+
return Number.isFinite(n) ? n : undefined;
|
|
12836
13073
|
}
|
|
12837
13074
|
const n = parseInt(m[1], 10);
|
|
12838
13075
|
const unit = m[2].toLowerCase();
|
|
@@ -13110,9 +13347,9 @@ function readFunctions(root, mode) {
|
|
|
13110
13347
|
const enabled = getBool(t, "enabled");
|
|
13111
13348
|
if (enabled !== undefined)
|
|
13112
13349
|
opts.enabled = enabled;
|
|
13113
|
-
const
|
|
13114
|
-
if (
|
|
13115
|
-
opts.verifyJwt =
|
|
13350
|
+
const verifyJwt = getBool(t, "verify_jwt");
|
|
13351
|
+
if (verifyJwt !== undefined)
|
|
13352
|
+
opts.verifyJwt = verifyJwt;
|
|
13116
13353
|
const entrypoint = getString(t, "entrypoint");
|
|
13117
13354
|
if (entrypoint !== undefined)
|
|
13118
13355
|
opts.entrypoint = entrypoint;
|
|
@@ -13434,12 +13671,12 @@ function keys(row, expected) {
|
|
|
13434
13671
|
}
|
|
13435
13672
|
}
|
|
13436
13673
|
function validTarget(value) {
|
|
13437
|
-
const
|
|
13438
|
-
keys(
|
|
13439
|
-
if (typeof
|
|
13674
|
+
const target = record(value);
|
|
13675
|
+
keys(target, ["environment", "projectRef"]);
|
|
13676
|
+
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)) {
|
|
13440
13677
|
throw new Error("Invalid migration binding target");
|
|
13441
13678
|
}
|
|
13442
|
-
return
|
|
13679
|
+
return target;
|
|
13443
13680
|
}
|
|
13444
13681
|
function validFile(file) {
|
|
13445
13682
|
return typeof file === "string" && /^[a-zA-Z0-9_./-]+\.sql$/.test(file) && !file.startsWith("/") && file.split("/").every((part) => part && part !== "." && part !== "..");
|
|
@@ -13451,7 +13688,7 @@ function parseMigrationBindingManifest(value) {
|
|
|
13451
13688
|
throw new Error("Invalid migration binding manifest");
|
|
13452
13689
|
}
|
|
13453
13690
|
const targets = manifest.targets.map(validTarget);
|
|
13454
|
-
if (new Set(targets.map((
|
|
13691
|
+
if (new Set(targets.map((target) => target.environment)).size !== targets.length) {
|
|
13455
13692
|
throw new Error("Duplicate migration binding environment");
|
|
13456
13693
|
}
|
|
13457
13694
|
const files = new Set;
|
|
@@ -13498,8 +13735,8 @@ function bindingValue(parameter, values) {
|
|
|
13498
13735
|
}
|
|
13499
13736
|
function renderMigrationBindings(options) {
|
|
13500
13737
|
const manifest = parseMigrationBindingManifest(options.manifest);
|
|
13501
|
-
const
|
|
13502
|
-
if (!manifest.targets.some((candidate) => candidate.environment ===
|
|
13738
|
+
const target = validTarget(options.target);
|
|
13739
|
+
if (!manifest.targets.some((candidate) => candidate.environment === target.environment && candidate.projectRef === target.projectRef)) {
|
|
13503
13740
|
throw new Error("Migration binding target is not registered in the manifest");
|
|
13504
13741
|
}
|
|
13505
13742
|
const sources = new Map;
|
|
@@ -13554,7 +13791,7 @@ function renderMigrationBindings(options) {
|
|
|
13554
13791
|
migrations,
|
|
13555
13792
|
attestation: {
|
|
13556
13793
|
schema: "supacloud.migration-binding-attestation.v1",
|
|
13557
|
-
...
|
|
13794
|
+
...target,
|
|
13558
13795
|
manifestSha256: migrationBindingSha256(JSON.stringify(manifest)),
|
|
13559
13796
|
files: files.sort((left, right) => left.file.localeCompare(right.file))
|
|
13560
13797
|
}
|
|
@@ -13685,14 +13922,14 @@ async function ensureProjectSecrets(paths) {
|
|
|
13685
13922
|
`, { mode: 384, flag: "wx" });
|
|
13686
13923
|
await link(temporaryFile, paths.secretsFile);
|
|
13687
13924
|
stored = candidate;
|
|
13688
|
-
} catch (
|
|
13689
|
-
if (
|
|
13690
|
-
throw
|
|
13925
|
+
} catch (error) {
|
|
13926
|
+
if (error.code !== "EEXIST")
|
|
13927
|
+
throw error;
|
|
13691
13928
|
stored = validateSecrets(JSON.parse(await readFile6(paths.secretsFile, "utf8")));
|
|
13692
13929
|
} finally {
|
|
13693
|
-
await unlink2(temporaryFile).catch((
|
|
13694
|
-
if (
|
|
13695
|
-
throw
|
|
13930
|
+
await unlink2(temporaryFile).catch((error) => {
|
|
13931
|
+
if (error.code !== "ENOENT")
|
|
13932
|
+
throw error;
|
|
13696
13933
|
});
|
|
13697
13934
|
}
|
|
13698
13935
|
}
|
|
@@ -14193,10 +14430,10 @@ async function stageDirectory(root, destination) {
|
|
|
14193
14430
|
throw error;
|
|
14194
14431
|
}
|
|
14195
14432
|
await mkdir5(destination, { recursive: true });
|
|
14196
|
-
const walk = async (current,
|
|
14433
|
+
const walk = async (current, target) => {
|
|
14197
14434
|
for (const entry of await readdir3(current, { withFileTypes: true })) {
|
|
14198
14435
|
const fullPath = join8(current, entry.name);
|
|
14199
|
-
const targetPath = join8(
|
|
14436
|
+
const targetPath = join8(target, entry.name);
|
|
14200
14437
|
if (entry.isSymbolicLink())
|
|
14201
14438
|
throw new Error(`snapshot refuses symbolic link: ${fullPath}`);
|
|
14202
14439
|
if (entry.isDirectory()) {
|
|
@@ -14211,9 +14448,9 @@ async function stageDirectory(root, destination) {
|
|
|
14211
14448
|
};
|
|
14212
14449
|
await walk(root, destination);
|
|
14213
14450
|
}
|
|
14214
|
-
async function stageFile(source,
|
|
14215
|
-
await mkdir5(dirname5(
|
|
14216
|
-
await copyFile(source,
|
|
14451
|
+
async function stageFile(source, target) {
|
|
14452
|
+
await mkdir5(dirname5(target), { recursive: true });
|
|
14453
|
+
await copyFile(source, target);
|
|
14217
14454
|
}
|
|
14218
14455
|
async function readManifest(payloadRoot) {
|
|
14219
14456
|
let parsed;
|
|
@@ -14285,9 +14522,9 @@ async function assertRestoreTargets(paths, manifest, force) {
|
|
|
14285
14522
|
if (manifest.includesLocalStorage && !isWithin(paths.stateDir, paths.storageDir))
|
|
14286
14523
|
targets.push(paths.storageDir);
|
|
14287
14524
|
if (!force) {
|
|
14288
|
-
for (const
|
|
14289
|
-
if (await directoryHasEntries(
|
|
14290
|
-
throw new Error(`restore target is not empty: ${
|
|
14525
|
+
for (const target of targets) {
|
|
14526
|
+
if (await directoryHasEntries(target))
|
|
14527
|
+
throw new Error(`restore target is not empty: ${target}; pass --force to replace it`);
|
|
14291
14528
|
}
|
|
14292
14529
|
}
|
|
14293
14530
|
}
|
|
@@ -14300,28 +14537,28 @@ async function directoryHasEntries(path) {
|
|
|
14300
14537
|
throw error;
|
|
14301
14538
|
}
|
|
14302
14539
|
}
|
|
14303
|
-
async function applyDirectorySwap(source,
|
|
14304
|
-
const targetInfo = await existingInfo(
|
|
14540
|
+
async function applyDirectorySwap(source, target, force, rollbackId, swaps) {
|
|
14541
|
+
const targetInfo = await existingInfo(target);
|
|
14305
14542
|
if (targetInfo && !targetInfo.isDirectory())
|
|
14306
|
-
throw new Error(`restore target is not a directory: ${
|
|
14307
|
-
const swap = { target
|
|
14543
|
+
throw new Error(`restore target is not a directory: ${target}`);
|
|
14544
|
+
const swap = { target };
|
|
14308
14545
|
if (targetInfo) {
|
|
14309
14546
|
if (!force) {
|
|
14310
|
-
if (await directoryHasEntries(
|
|
14311
|
-
throw new Error(`restore target is not empty: ${
|
|
14312
|
-
await rm4(
|
|
14547
|
+
if (await directoryHasEntries(target))
|
|
14548
|
+
throw new Error(`restore target is not empty: ${target}; pass --force to replace it`);
|
|
14549
|
+
await rm4(target, { recursive: true, force: true });
|
|
14313
14550
|
} else {
|
|
14314
|
-
swap.rollbackPath = join8(dirname5(
|
|
14315
|
-
await rename2(
|
|
14551
|
+
swap.rollbackPath = join8(dirname5(target), `.${target.split(sep2).pop() ?? "state"}.restore-${rollbackId}`);
|
|
14552
|
+
await rename2(target, swap.rollbackPath);
|
|
14316
14553
|
}
|
|
14317
14554
|
}
|
|
14318
14555
|
try {
|
|
14319
|
-
await mkdir5(dirname5(
|
|
14320
|
-
await rename2(source,
|
|
14556
|
+
await mkdir5(dirname5(target), { recursive: true });
|
|
14557
|
+
await rename2(source, target);
|
|
14321
14558
|
swaps.push(swap);
|
|
14322
14559
|
} catch (error) {
|
|
14323
14560
|
if (swap.rollbackPath)
|
|
14324
|
-
await rename2(swap.rollbackPath,
|
|
14561
|
+
await rename2(swap.rollbackPath, target).catch(() => {});
|
|
14325
14562
|
throw error;
|
|
14326
14563
|
}
|
|
14327
14564
|
}
|
|
@@ -14341,17 +14578,17 @@ async function existingInfo(path) {
|
|
|
14341
14578
|
throw error;
|
|
14342
14579
|
}
|
|
14343
14580
|
}
|
|
14344
|
-
async function copyEntry(source,
|
|
14581
|
+
async function copyEntry(source, target) {
|
|
14345
14582
|
const info = await lstat3(source);
|
|
14346
14583
|
if (info.isSymbolicLink())
|
|
14347
14584
|
throw new Error(`snapshot refuses symbolic link: ${source}`);
|
|
14348
14585
|
if (info.isDirectory()) {
|
|
14349
|
-
await mkdir5(
|
|
14586
|
+
await mkdir5(target, { recursive: true });
|
|
14350
14587
|
for (const entry of await readdir3(source))
|
|
14351
|
-
await copyEntry(join8(source, entry), join8(
|
|
14588
|
+
await copyEntry(join8(source, entry), join8(target, entry));
|
|
14352
14589
|
} else if (info.isFile()) {
|
|
14353
|
-
await mkdir5(dirname5(
|
|
14354
|
-
await Bun.write(
|
|
14590
|
+
await mkdir5(dirname5(target), { recursive: true });
|
|
14591
|
+
await Bun.write(target, Bun.file(source));
|
|
14355
14592
|
} else
|
|
14356
14593
|
throw new Error(`snapshot refuses unsupported filesystem entry: ${source}`);
|
|
14357
14594
|
}
|