@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/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.14.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.0",
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.0"
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
- userId = created.rows[0].id;
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 dataCapacityBits2 = getNumDataCodewords(version) * 8;
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 <= dataCapacityBits2)
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 res2 = await this.db.query(`insert into auth.users (aud, role, raw_app_meta_data, raw_user_meta_data, is_anonymous, last_sign_in_at)
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(res2.rows[0]));
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 IS NULL
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
- RETURN normalized_queue_name;
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 bigint
4090
+ RETURNS SETOF text
3948
4091
  LANGUAGE sql
3949
4092
  VOLATILE
3950
4093
  SECURITY DEFINER
3951
4094
  SET search_path = ''
3952
- AS $$ SELECT * FROM pgmq.send(pgmq_public.require_public_queue(queue_name), message, sleep_seconds); $$;
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 bigint
4098
+ RETURNS SETOF text
3956
4099
  LANGUAGE sql
3957
4100
  VOLATILE
3958
4101
  SECURITY DEFINER
3959
4102
  SET search_path = ''
3960
- AS $$ SELECT * FROM pgmq.send_batch(pgmq_public.require_public_queue(queue_name), messages, sleep_seconds); $$;
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 SETOF pgmq.message_record
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 $$ SELECT * FROM pgmq.read(pgmq_public.require_public_queue(queue_name), sleep_seconds, n); $$;
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 SETOF pgmq.message_record
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 $$ SELECT * FROM pgmq.pop(pgmq_public.require_public_queue(queue_name)); $$;
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
- RETURN supacloud_workflows.snapshot(current_step.run_id, true);
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 public.supacloud_command_submit(request jsonb)
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
- RETURN supacloud_commands.snapshot(command_id_text::uuid, false);
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 decoder2 = new TextDecoder;
7126
+ const decoder = new TextDecoder;
6890
7127
  const read = (len) => {
6891
- const out = decoder2.decode(bytes.subarray(offset, offset + len));
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(decoder2.decode(payloadBytes)) : payloadBytes
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 path2 = segments.slice(0, -1);
7452
+ const path = segments.slice(0, -1);
7216
7453
  for (const term of splitTopLevel(value, ",")) {
7217
- q.order.push(parseOrderTerm(term.trim(), path2));
7454
+ q.order.push(parseOrderTerm(term.trim(), path));
7218
7455
  }
7219
7456
  continue;
7220
7457
  }
7221
7458
  if (last === "limit" || last === "offset") {
7222
- const path2 = segments.slice(0, -1).join(".");
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(path2, n);
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 path2 = segments.slice(0, negated ? -2 : -1);
7232
- q.conditions.push(parseLogicTree(last, value, path2, negated));
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 expr2 = renderColumnExpr(childAlias, c.name);
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 ${expr2}${cast} from ${fromClause}${where}) as ${quoteIdent(out)}` : `coalesce((select json_agg(${expr2}${cast}${order}) from ${fromClause}${where}), ${AGG_EMPTY}) as ${quoteIdent(out)}`;
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 innerCond2 = embed.inner ? `exists (select 1 from ${fromClause}${where})` : undefined;
7678
- return { exprs, innerCond: innerCond2 };
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 count2 = null;
8374
+ let count = null;
8138
8375
  if (built.countSql) {
8139
8376
  const c = await query(built.countSql, []);
8140
- count2 = c.rows[0].count;
8377
+ count = c.rows[0].count;
8141
8378
  }
8142
- return { rows: res.rows[0].body, count: count2 };
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 count2 = null;
8575
+ let count = null;
8339
8576
  if (countSql) {
8340
8577
  const c = await query(countSql, []);
8341
- count2 = c.rows[0].count;
8578
+ count = c.rows[0].count;
8342
8579
  }
8343
- return { rows: res.rows[0].body, count: count2 };
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((resolve2) => this.waiters.push(resolve2));
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 bucket2 = parts[3];
8866
- const key2 = parts.slice(4).join("/");
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(bucket2, key2);
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, bucket2, key2);
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, bucket2, key2);
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 bucket2 = parts[3];
8915
- const key2 = parts.slice(4).join("/");
9151
+ const bucket = parts[3];
9152
+ const key = parts.slice(4).join("/");
8916
9153
  if (method === "POST")
8917
- return await this.signUploadUrl(ctx, bucket2, key2);
9154
+ return await this.signUploadUrl(ctx, bucket, key);
8918
9155
  if (method === "PUT")
8919
- return await this.redeemSignedUpload(req, url, bucket2, key2);
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((resolve2) => {
8955
- release = resolve2;
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 uploadId2 = crypto.randomUUID();
9208
- this.tusUploads.set(uploadId2, {
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/${uploadId2}`,
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((upload3) => upload3.completedAt === undefined).length;
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 upload2 = {
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, upload2);
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, upload2, 0, chunk);
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(upload2.offset)
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 ctx2 = { role: String(keyClaims.role ?? "anon"), claims: keyClaims };
11072
- return withCors(await auth.handle(req, ctx2, url));
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 unsupported2 = tables.filter((table) => table.present && !["r", "p"].includes(table.relkind ?? ""));
11463
- if (unsupported2.length > 0) {
11464
- throw new Error(`PowerSync publication only accepts ordinary or partitioned tables: ${unsupported2.map((table) => table.name).join(", ")}`);
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((resolve3, reject) => {
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
- resolve3();
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((resolve3, reject) => {
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: resolve3, reject, results: [], columns: [], error: null };
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((resolve3) => {
12056
+ return new Promise((resolve) => {
11820
12057
  if (this.closed)
11821
- return resolve3();
12058
+ return resolve();
11822
12059
  this.socket.write(message(88, Buffer.alloc(0)));
11823
- this.socket.end(() => resolve3());
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 iso3 = text.replace(" ", "T");
11995
- if (/[+-]\d\d$/.test(iso3))
11996
- iso3 += ":00";
11997
- return new Date(iso3);
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 response2 = await callback(transactionClient);
12323
+ const response = await callback(transactionClient);
12087
12324
  await queryClient.exec("commit");
12088
- return response2;
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 response2 = await fetch(url, { signal: AbortSignal.timeout(30000) });
12318
- if (response2.ok || response2.status < 500)
12319
- return response2;
12320
- lastError = new Error(`failed to download ${url}: HTTP ${response2.status}`);
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((resolve3) => setTimeout(resolve3, attempt * 500));
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((resolve3) => setTimeout(resolve3, 150));
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((resolve3) => {
12682
+ await new Promise((resolve) => {
12446
12683
  const killTimeout = setTimeout(() => {
12447
12684
  postgres.kill("SIGKILL");
12448
- resolve3();
12685
+ resolve();
12449
12686
  }, 5000);
12450
12687
  postgres.once("exit", () => {
12451
12688
  clearTimeout(killTimeout);
12452
- resolve3();
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 n2 = parseInt(v, 10);
12835
- return Number.isFinite(n2) ? n2 : undefined;
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 verifyJwt2 = getBool(t, "verify_jwt");
13114
- if (verifyJwt2 !== undefined)
13115
- opts.verifyJwt = verifyJwt2;
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 target2 = record(value);
13438
- keys(target2, ["environment", "projectRef"]);
13439
- if (typeof target2.environment !== "string" || !/^[a-z][a-z0-9-]{0,62}$/.test(target2.environment) || typeof target2.projectRef !== "string" || !/^[a-z0-9][a-z0-9_-]{0,62}$/.test(target2.projectRef)) {
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 target2;
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((target2) => target2.environment)).size !== targets.length) {
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 target2 = validTarget(options.target);
13502
- if (!manifest.targets.some((candidate) => candidate.environment === target2.environment && candidate.projectRef === target2.projectRef)) {
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
- ...target2,
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 (error2) {
13689
- if (error2.code !== "EEXIST")
13690
- throw error2;
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((error2) => {
13694
- if (error2.code !== "ENOENT")
13695
- throw error2;
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, target2) => {
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(target2, entry.name);
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, target2) {
14215
- await mkdir5(dirname5(target2), { recursive: true });
14216
- await copyFile(source, target2);
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 target2 of targets) {
14289
- if (await directoryHasEntries(target2))
14290
- throw new Error(`restore target is not empty: ${target2}; pass --force to replace it`);
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, target2, force, rollbackId, swaps) {
14304
- const targetInfo = await existingInfo(target2);
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: ${target2}`);
14307
- const swap = { target: target2 };
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(target2))
14311
- throw new Error(`restore target is not empty: ${target2}; pass --force to replace it`);
14312
- await rm4(target2, { recursive: true, force: true });
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(target2), `.${target2.split(sep2).pop() ?? "state"}.restore-${rollbackId}`);
14315
- await rename2(target2, swap.rollbackPath);
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(target2), { recursive: true });
14320
- await rename2(source, target2);
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, target2).catch(() => {});
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, target2) {
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(target2, { recursive: true });
14586
+ await mkdir5(target, { recursive: true });
14350
14587
  for (const entry of await readdir3(source))
14351
- await copyEntry(join8(source, entry), join8(target2, entry));
14588
+ await copyEntry(join8(source, entry), join8(target, entry));
14352
14589
  } else if (info.isFile()) {
14353
- await mkdir5(dirname5(target2), { recursive: true });
14354
- await Bun.write(target2, Bun.file(source));
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
  }