@supacloud/lite 0.25.1 → 0.26.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 +7 -0
- package/README.md +91 -7
- package/THIRD_PARTY_NOTICES.md +10 -0
- package/dist/cli.js +113 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +108 -10
- package/dist/runtime/db/bootstrap.d.ts +1 -1
- package/dist/runtime/db/bootstrap.d.ts.map +1 -1
- package/dist/runtime/db/database.d.ts.map +1 -1
- package/dist/runtime/db/emulated.d.ts +11 -6
- package/dist/runtime/db/emulated.d.ts.map +1 -1
- package/dist/runtime/db/pglite-engine.d.ts.map +1 -1
- package/dist/runtime/node/native/extensions.d.ts +21 -0
- package/dist/runtime/node/native/extensions.d.ts.map +1 -0
- package/dist/runtime/node/native/readiness.d.ts +4 -0
- package/dist/runtime/node/native/readiness.d.ts.map +1 -1
- package/dist/standalone-assets-protocol.d.ts +1 -1
- package/dist/standalone-assets-protocol.d.ts.map +1 -1
- package/package.json +6 -2
|
@@ -17,5 +17,5 @@ export declare const MINIMAL_BOOTSTRAP_SQL = "\ncreate schema if not exists auth
|
|
|
17
17
|
* schema, storage schema with default RLS policies, migration bookkeeping, and
|
|
18
18
|
* the realtime CDC + broadcast plumbing.
|
|
19
19
|
*/
|
|
20
|
-
export declare const BOOTSTRAP_SQL = "\n-- \u2500\u2500 Extensions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n-- Supabase enables these by default and migrations lean on them\n-- (uuid_generate_v4(), crypt(), citext, pg_trgm, \u2026). Each is created into the\n-- 'extensions' schema like hosted Supabase; any not available in this engine\n-- build is skipped rather than aborting the whole bootstrap.\ncreate schema if not exists extensions;\ndo $$\ndeclare ext text;\nbegin\n foreach ext in array array['uuid-ossp','pgcrypto','citext','pg_trgm','ltree','hstore','fuzzystrmatch'] loop\n begin\n execute format('create extension if not exists %I with schema extensions', ext);\n exception when others then\n -- extension not bundled in this engine build; continue\n end;\n end loop;\nend $$;\n\n-- uuid-ossp isn't in every Postgres build (it needs an external UUID lib at\n-- build time - e.g. the theseus Linux binaries omit it). uuid_generate_v4() is\n-- the single most-used function from it, so shim it onto core gen_random_uuid()\n-- (also a v4 UUID) whenever the real extension isn't present, so migrations\n-- that call it work identically on every engine and platform.\ndo $$\nbegin\n if not exists (\n select 1 from pg_proc p join pg_namespace n on n.oid = p.pronamespace\n where p.proname = 'uuid_generate_v4' and n.nspname in ('public', 'extensions')\n ) then\n create function extensions.uuid_generate_v4() returns uuid\n language sql volatile as 'select gen_random_uuid()';\n end if;\nend $$;\n\n-- Make extension functions resolvable unqualified (uuid_generate_v4(), \u2026) on\n-- the current session (migrations run here) and for future connections.\ndo $$\nbegin\n execute 'alter database ' || quote_ident(current_database()) ||\n ' set search_path to \"$user\", public, extensions';\nexception when others then\n -- some engines disallow altering the current database; session SET below still applies\nend $$;\nset search_path to \"$user\", public, extensions;\n\n-- \u2500\u2500 Roles \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndo $$\nbegin\n if not exists (select from pg_roles where rolname = 'anon') then\n create role anon nologin noinherit;\n end if;\n if not exists (select from pg_roles where rolname = 'authenticated') then\n create role authenticated nologin noinherit;\n end if;\n if not exists (select from pg_roles where rolname = 'service_role') then\n create role service_role nologin noinherit bypassrls;\n end if;\nend $$;\n\ngrant usage on schema extensions to anon, authenticated, service_role;\nalter role anon set search_path to \"$user\", public, extensions;\nalter role authenticated set search_path to \"$user\", public, extensions;\nalter role service_role set search_path to \"$user\", public, extensions;\n\n-- Initialize defaults once. Restart/maintenance must not undo application ACLs.\ndo $$\nbegin\n if to_regclass('supabase_migrations.schema_migrations') is null then\n grant usage on schema public to anon, authenticated, service_role;\n alter default privileges in schema public\n grant all on tables to anon, authenticated, service_role;\n alter default privileges in schema public\n grant all on sequences to anon, authenticated, service_role;\n end if;\nend $$;\n\n-- PostgreSQL already grants EXECUTE on new functions to PUBLIC. Keep that\n-- default so project migrations can revoke PUBLIC and grant only selected\n-- roles without hidden direct grants from Lite. Existing ACLs are left intact.\n\n-- \u2500\u2500 Auth schema (GoTrue-compatible subset) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ncreate schema if not exists auth;\n\ncreate table if not exists auth.users (\n instance_id uuid default '00000000-0000-0000-0000-000000000000',\n id uuid primary key default gen_random_uuid(),\n aud text default 'authenticated',\n role text default 'authenticated',\n email text unique,\n encrypted_password text,\n email_confirmed_at timestamptz,\n invited_at timestamptz,\n confirmation_token text default '',\n confirmation_sent_at timestamptz,\n recovery_token text default '',\n recovery_sent_at timestamptz,\n email_change_token_new text default '',\n email_change text default '',\n email_change_sent_at timestamptz,\n email_change_token_current text default '',\n email_change_confirm_status smallint default 0,\n last_sign_in_at timestamptz,\n raw_app_meta_data jsonb default '{}'::jsonb,\n raw_user_meta_data jsonb default '{}'::jsonb,\n is_super_admin boolean default false,\n created_at timestamptz default now(),\n updated_at timestamptz default now(),\n phone text unique,\n phone_confirmed_at timestamptz,\n phone_change text default '',\n phone_change_token text default '',\n phone_change_sent_at timestamptz,\n reauthentication_token text default '',\n reauthentication_sent_at timestamptz,\n banned_until timestamptz,\n deleted_at timestamptz,\n is_anonymous boolean default false\n);\n\ncreate table if not exists auth.refresh_tokens (\n id bigint generated by default as identity primary key,\n token text unique not null,\n user_id uuid not null references auth.users(id) on delete cascade,\n parent text,\n session_id uuid,\n revoked boolean default false,\n created_at timestamptz default now(),\n updated_at timestamptz default now()\n);\n\ncreate index if not exists refresh_tokens_user_id_idx on auth.refresh_tokens(user_id);\n\ncreate table if not exists auth.one_time_tokens (\n id uuid primary key default gen_random_uuid(),\n user_id uuid references auth.users(id) on delete cascade,\n email text,\n phone text,\n token_type text not null, -- otp | magiclink | recovery | sms\n token text not null,\n attempts int not null default 0,\n created_at timestamptz default now(),\n expires_at timestamptz not null,\n constraint one_time_tokens_contact_check check (\n (email is not null and phone is null) or (email is null and phone is not null)\n )\n);\n\n-- Upgrade databases created by Lite <=0.5.9 without touching existing email tokens.\nalter table auth.one_time_tokens add column if not exists phone text;\nalter table auth.one_time_tokens alter column email drop not null;\ndo $phone_otp_contact_constraint$\nbegin\n if not exists (\n select 1 from pg_constraint\n where conrelid = 'auth.one_time_tokens'::regclass\n and conname = 'one_time_tokens_contact_check'\n ) then\n alter table auth.one_time_tokens add constraint one_time_tokens_contact_check check (\n (email is not null and phone is null) or (email is null and phone is not null)\n );\n end if;\nend $phone_otp_contact_constraint$;\n\ncreate unique index if not exists one_time_tokens_phone_type_idx\n on auth.one_time_tokens(phone, token_type) where phone is not null;\n\n-- Only a keyed phone fingerprint is persisted for cooldown enforcement; the\n-- normalized phone number never enters this table.\ncreate table if not exists auth.phone_otp_cooldowns (\n phone_fingerprint text primary key,\n issuance_id uuid not null,\n last_sent_at timestamptz not null default now()\n);\n\ncreate table if not exists auth.identities (\n id uuid primary key default gen_random_uuid(),\n user_id uuid not null references auth.users(id) on delete cascade,\n provider text not null,\n provider_id text not null,\n identity_data jsonb default '{}'::jsonb,\n last_sign_in_at timestamptz default now(),\n created_at timestamptz default now(),\n updated_at timestamptz default now(),\n unique (provider, provider_id)\n);\n\n-- OAuth / PKCE flow state, bridging /authorize \u2192 provider \u2192 /callback \u2192 exchange\ncreate table if not exists auth.flow_state (\n id uuid primary key default gen_random_uuid(),\n provider text not null,\n provider_state text not null unique,\n redirect_to text,\n code_challenge text,\n code_challenge_method text,\n auth_code text unique,\n user_id uuid references auth.users(id) on delete cascade,\n created_at timestamptz default now(),\n expires_at timestamptz not null\n);\n\ncreate table if not exists auth.mfa_factors (\n id uuid primary key default gen_random_uuid(),\n user_id uuid not null references auth.users(id) on delete cascade,\n friendly_name text,\n factor_type text not null default 'totp',\n status text not null default 'unverified', -- unverified | verified\n secret text,\n created_at timestamptz default now(),\n updated_at timestamptz default now()\n);\n\ncreate table if not exists auth.mfa_challenges (\n id uuid primary key default gen_random_uuid(),\n factor_id uuid not null references auth.mfa_factors(id) on delete cascade,\n verified_at timestamptz,\n created_at timestamptz default now(),\n expires_at timestamptz not null\n);\n\n-- append-only security audit trail (GoTrue-compatible shape)\ncreate table if not exists auth.audit_log_entries (\n instance_id uuid default '00000000-0000-0000-0000-000000000000',\n id uuid primary key default gen_random_uuid(),\n payload jsonb,\n created_at timestamptz default now(),\n ip_address varchar(64) not null default ''\n);\ncreate index if not exists audit_logs_instance_id_idx on auth.audit_log_entries(created_at);\n\n-- runtime-mutable instance settings (Studio auth toggles)\ncreate table if not exists auth.config (\n key text primary key,\n value jsonb not null,\n updated_at timestamptz default now()\n);\n\ngrant usage on schema auth to anon, authenticated, service_role;\n\ncreate or replace function auth.jwt() returns jsonb\nlanguage sql stable as $$\n select coalesce(\n nullif(current_setting('request.jwt.claims', true), ''),\n '{}'\n )::jsonb\n$$;\n\ncreate or replace function auth.uid() returns uuid\nlanguage sql stable as $$\n select nullif(auth.jwt() ->> 'sub', '')::uuid\n$$;\n\ncreate or replace function auth.role() returns text\nlanguage sql stable as $$\n select coalesce(auth.jwt() ->> 'role', 'anon')\n$$;\n\ncreate or replace function auth.email() returns text\nlanguage sql stable as $$\n select auth.jwt() ->> 'email'\n$$;\n\ngrant execute on function auth.jwt(), auth.uid(), auth.role(), auth.email()\n to anon, authenticated, service_role;\n\n-- \u2500\u2500 Storage schema (storage-api-compatible subset) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ncreate schema if not exists storage;\n\ncreate table if not exists storage.buckets (\n id text primary key,\n name text not null unique,\n owner uuid,\n owner_id text,\n public boolean default false,\n file_size_limit bigint,\n allowed_mime_types text[],\n created_at timestamptz default now(),\n updated_at timestamptz default now()\n);\n\nalter table storage.buckets add column if not exists owner_id text;\nupdate storage.buckets\nset owner_id = owner::text\nwhere owner_id is null and owner is not null;\n\ncreate table if not exists storage.objects (\n id uuid primary key default gen_random_uuid(),\n bucket_id text not null references storage.buckets(id),\n name text not null,\n owner uuid,\n owner_id text,\n version text,\n metadata jsonb default '{}'::jsonb,\n created_at timestamptz default now(),\n updated_at timestamptz default now(),\n last_accessed_at timestamptz default now(),\n unique (bucket_id, name)\n);\n\n-- storage-api keeps the legacy UUID owner and the current text owner_id in\n-- parallel. Re-running bootstrap upgrades existing Lite databases and retains\n-- object ownership for rows created before owner_id support was added.\nalter table storage.objects add column if not exists owner_id text;\nupdate storage.objects\nset owner_id = owner::text\nwhere owner_id is null and owner is not null;\n\ncreate index if not exists objects_bucket_name_idx on storage.objects(bucket_id, name);\n\ngrant usage on schema storage to anon, authenticated, service_role;\ngrant select on storage.buckets to anon, authenticated, service_role;\ngrant all on storage.buckets to service_role;\ngrant all on storage.objects to anon, authenticated, service_role;\n\ncreate or replace function storage.foldername(name text) returns text[]\nlanguage sql immutable as $$\n select (string_to_array(name, '/'))[1 : array_length(string_to_array(name, '/'), 1) - 1]\n$$;\n\ncreate or replace function storage.filename(name text) returns text\nlanguage sql immutable as $$\n select (string_to_array(name, '/'))[array_length(string_to_array(name, '/'), 1)]\n$$;\n\ncreate or replace function storage.extension(name text) returns text\nlanguage sql immutable as $$\n select reverse(split_part(reverse(storage.filename(name)), '.', 1))\n$$;\n\nalter table storage.objects enable row level security;\n\ndrop policy if exists supacloud_lite_authenticated_all on storage.objects;\ndrop policy if exists supacloud_lite_public_read on storage.objects;\n\ndo $legacy_policy_cleanup$\ndeclare\n legacy_prefix constant text := 'tin' || 'base';\nbegin\n execute format(\n 'drop policy if exists %I on storage.objects',\n legacy_prefix || '_authenticated_all'\n );\n execute format(\n 'drop policy if exists %I on storage.objects',\n legacy_prefix || '_public_read'\n );\nend $legacy_policy_cleanup$;\n\n-- \u2500\u2500 Migration bookkeeping (same table the Supabase CLI uses) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ncreate schema if not exists supabase_migrations;\n\ncreate table if not exists supabase_migrations.schema_migrations (\n version text primary key,\n name text,\n statements text[],\n applied_at timestamptz default now()\n);\n\ncreate table if not exists supabase_migrations.seed_files (\n path text primary key,\n hash text,\n applied_at timestamptz default now()\n);\n\n-- \u2500\u2500 Realtime CDC plumbing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndo $legacy_schema_upgrade$\ndeclare\n legacy_schema constant text := 'tin' || 'base';\nbegin\n if exists (select 1 from pg_namespace where nspname = legacy_schema)\n and not exists (select 1 from pg_namespace where nspname = 'supacloud_lite') then\n execute format('alter schema %I rename to supacloud_lite', legacy_schema);\n end if;\nend $legacy_schema_upgrade$;\n\ncreate schema if not exists supacloud_lite;\n\ncreate or replace function supacloud_lite.cdc_notify() returns trigger\nlanguage plpgsql security definer as $$\ndeclare\n payload text;\nbegin\n payload := json_build_object(\n 'schema', TG_TABLE_SCHEMA,\n 'table', TG_TABLE_NAME,\n 'type', TG_OP,\n 'commit_timestamp', to_char(now() at time zone 'utc', 'YYYY-MM-DD\"T\"HH24:MI:SS.MS\"Z\"'),\n 'record', case when TG_OP in ('INSERT', 'UPDATE') then row_to_json(NEW) else null end,\n 'old_record', case when TG_OP in ('UPDATE', 'DELETE') then row_to_json(OLD) else null end\n )::text;\n -- pg_notify payloads are capped at ~8kB; degrade like Supabase does.\n if octet_length(payload) > 7500 then\n payload := json_build_object(\n 'schema', TG_TABLE_SCHEMA,\n 'table', TG_TABLE_NAME,\n 'type', TG_OP,\n 'commit_timestamp', to_char(now() at time zone 'utc', 'YYYY-MM-DD\"T\"HH24:MI:SS.MS\"Z\"'),\n 'record', null,\n 'old_record', null,\n 'errors', json_build_array('Payload too large')\n )::text;\n end if;\n perform pg_notify('supacloud_lite_cdc', payload);\n return coalesce(NEW, OLD);\nend $$;\n\ndo $legacy_trigger_upgrade$\ndeclare\n legacy_schema constant text := 'tin' || 'base';\n legacy_trigger constant text := ('tin' || 'base') || '_cdc';\n trigger_record record;\n target_exists boolean;\nbegin\n for trigger_record in\n select c.oid as relation_oid, n.nspname as schema_name, c.relname as table_name\n from pg_trigger tg\n join pg_class c on c.oid = tg.tgrelid\n join pg_namespace n on n.oid = c.relnamespace\n join pg_proc p on p.oid = tg.tgfoid\n join pg_namespace pn on pn.oid = p.pronamespace\n where not tg.tgisinternal\n and tg.tgname = legacy_trigger\n and p.proname = 'cdc_notify'\n and pn.nspname in (legacy_schema, 'supacloud_lite')\n loop\n select exists (\n select 1\n from pg_trigger\n where not tgisinternal\n and tgrelid = trigger_record.relation_oid\n and tgname = 'supacloud_lite_cdc'\n ) into target_exists;\n\n execute format(\n 'drop trigger %I on %I.%I',\n legacy_trigger,\n trigger_record.schema_name,\n trigger_record.table_name\n );\n\n if not target_exists then\n execute format(\n 'create trigger supacloud_lite_cdc after insert or update or delete on %I.%I for each row execute function supacloud_lite.cdc_notify()',\n trigger_record.schema_name,\n trigger_record.table_name\n );\n end if;\n end loop;\nend $legacy_trigger_upgrade$;\n\ndo $legacy_schema_cleanup$\ndeclare\n legacy_schema constant text := 'tin' || 'base';\nbegin\n if exists (\n select 1\n from pg_proc p\n join pg_namespace n on n.oid = p.pronamespace\n where n.nspname = legacy_schema\n and p.proname = 'cdc_notify'\n and p.pronargs = 0\n ) then\n execute format('drop function %I.cdc_notify()', legacy_schema);\n end if;\n\n if exists (select 1 from pg_namespace where nspname = legacy_schema) then\n begin\n execute format('drop schema %I', legacy_schema);\n exception when dependent_objects_still_exist then\n raise notice 'SupaCloud Lite: retained non-empty legacy runtime schema';\n end;\n end if;\nend $legacy_schema_cleanup$;\n\n-- \u2500\u2500 Realtime Authorization + broadcast-from-database \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n-- Mirrors Supabase Realtime: developers write RLS policies on\n-- realtime.messages using realtime.topic(); a SELECT policy grants a private\n-- channel's subscribers the right to *receive*, an INSERT policy the right to\n-- *broadcast*. realtime.send() lets the database push a broadcast to a topic.\ncreate schema if not exists realtime;\n\ncreate table if not exists realtime.messages (\n id uuid not null default gen_random_uuid(),\n topic text not null,\n extension text not null,\n event text,\n payload jsonb,\n private boolean default false,\n inserted_at timestamptz not null default now(),\n updated_at timestamptz not null default now(),\n primary key (id)\n);\nalter table realtime.messages enable row level security;\n\ngrant usage on schema realtime to anon, authenticated, service_role;\ngrant all on realtime.messages to anon, authenticated, service_role;\n\n-- the topic currently being authorized/broadcast (set per operation)\ncreate or replace function realtime.topic() returns text\n language sql stable as $$ select nullif(current_setting('realtime.topic', true), '') $$;\n\n-- push a broadcast message to all subscribers of <topic> from SQL / triggers.\n-- Delivered over the websocket by the in-process realtime engine, which listens\n-- on the 'supacloud_lite_realtime_broadcast' channel.\ncreate or replace function realtime.send(payload jsonb, event text, topic text, private boolean default true)\n returns void language plpgsql security definer as $$\nbegin\n perform pg_notify(\n 'supacloud_lite_realtime_broadcast',\n json_build_object('topic', topic, 'event', event, 'payload', payload, 'private', private)::text\n );\nexception when others then\n -- never let a notify failure abort the caller's transaction\n null;\nend $$;\n\ngrant execute on function realtime.topic() to anon, authenticated, service_role;\ngrant execute on function realtime.send(jsonb, text, text, boolean) to anon, authenticated, service_role;\n";
|
|
20
|
+
export declare const BOOTSTRAP_SQL = "\n-- \u2500\u2500 Extensions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n-- Supabase enables these by default and migrations lean on them\n-- (uuid_generate_v4(), crypt(), citext, pg_trgm, \u2026). Each is created into the\n-- 'extensions' schema like hosted Supabase; any not available in this engine\n-- build is skipped rather than aborting the whole bootstrap.\ncreate schema if not exists extensions;\ndo $$\ndeclare ext text;\nbegin\n foreach ext in array array[\n 'uuid-ossp','pgcrypto','citext','pg_trgm','ltree','hstore','fuzzystrmatch',\n 'vector','postgis','pg_textsearch'\n ] loop\n begin\n execute format('create extension if not exists %I with schema extensions', ext);\n exception when others then\n -- extension not bundled in this engine build; continue\n end;\n end loop;\nend $$;\n\n-- The official pgmq extension owns its required pgmq schema. PGlite and\n-- native PostgreSQL builds that do not provide it simply continue to Lite's\n-- SQL fallback during database initialization.\ncreate schema if not exists pgmq;\ndo $$\nbegin\n begin\n create extension if not exists pgmq with schema pgmq;\n exception when others then\n null;\n end;\nend $$;\n\n-- uuid-ossp isn't in every Postgres build (it needs an external UUID lib at\n-- build time - e.g. the theseus Linux binaries omit it). uuid_generate_v4() is\n-- the single most-used function from it, so shim it onto core gen_random_uuid()\n-- (also a v4 UUID) whenever the real extension isn't present, so migrations\n-- that call it work identically on every engine and platform.\ndo $$\nbegin\n if not exists (\n select 1 from pg_proc p join pg_namespace n on n.oid = p.pronamespace\n where p.proname = 'uuid_generate_v4' and n.nspname in ('public', 'extensions')\n ) then\n create function extensions.uuid_generate_v4() returns uuid\n language sql volatile as 'select gen_random_uuid()';\n end if;\nend $$;\n\n-- Make extension functions resolvable unqualified (uuid_generate_v4(), \u2026) on\n-- the current session (migrations run here) and for future connections.\ndo $$\nbegin\n execute 'alter database ' || quote_ident(current_database()) ||\n ' set search_path to \"$user\", public, extensions';\nexception when others then\n -- some engines disallow altering the current database; session SET below still applies\nend $$;\nset search_path to \"$user\", public, extensions;\n\n-- \u2500\u2500 Roles \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndo $$\nbegin\n if not exists (select from pg_roles where rolname = 'anon') then\n create role anon nologin noinherit;\n end if;\n if not exists (select from pg_roles where rolname = 'authenticated') then\n create role authenticated nologin noinherit;\n end if;\n if not exists (select from pg_roles where rolname = 'service_role') then\n create role service_role nologin noinherit bypassrls;\n end if;\nend $$;\n\ngrant usage on schema extensions to anon, authenticated, service_role;\nalter role anon set search_path to \"$user\", public, extensions;\nalter role authenticated set search_path to \"$user\", public, extensions;\nalter role service_role set search_path to \"$user\", public, extensions;\n\n-- Initialize defaults once. Restart/maintenance must not undo application ACLs.\ndo $$\nbegin\n if to_regclass('supabase_migrations.schema_migrations') is null then\n grant usage on schema public to anon, authenticated, service_role;\n alter default privileges in schema public\n grant all on tables to anon, authenticated, service_role;\n alter default privileges in schema public\n grant all on sequences to anon, authenticated, service_role;\n end if;\nend $$;\n\n-- PostgreSQL already grants EXECUTE on new functions to PUBLIC. Keep that\n-- default so project migrations can revoke PUBLIC and grant only selected\n-- roles without hidden direct grants from Lite. Existing ACLs are left intact.\n\n-- \u2500\u2500 Auth schema (GoTrue-compatible subset) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ncreate schema if not exists auth;\n\ncreate table if not exists auth.users (\n instance_id uuid default '00000000-0000-0000-0000-000000000000',\n id uuid primary key default gen_random_uuid(),\n aud text default 'authenticated',\n role text default 'authenticated',\n email text unique,\n encrypted_password text,\n email_confirmed_at timestamptz,\n invited_at timestamptz,\n confirmation_token text default '',\n confirmation_sent_at timestamptz,\n recovery_token text default '',\n recovery_sent_at timestamptz,\n email_change_token_new text default '',\n email_change text default '',\n email_change_sent_at timestamptz,\n email_change_token_current text default '',\n email_change_confirm_status smallint default 0,\n last_sign_in_at timestamptz,\n raw_app_meta_data jsonb default '{}'::jsonb,\n raw_user_meta_data jsonb default '{}'::jsonb,\n is_super_admin boolean default false,\n created_at timestamptz default now(),\n updated_at timestamptz default now(),\n phone text unique,\n phone_confirmed_at timestamptz,\n phone_change text default '',\n phone_change_token text default '',\n phone_change_sent_at timestamptz,\n reauthentication_token text default '',\n reauthentication_sent_at timestamptz,\n banned_until timestamptz,\n deleted_at timestamptz,\n is_anonymous boolean default false\n);\n\ncreate table if not exists auth.refresh_tokens (\n id bigint generated by default as identity primary key,\n token text unique not null,\n user_id uuid not null references auth.users(id) on delete cascade,\n parent text,\n session_id uuid,\n revoked boolean default false,\n created_at timestamptz default now(),\n updated_at timestamptz default now()\n);\n\ncreate index if not exists refresh_tokens_user_id_idx on auth.refresh_tokens(user_id);\n\ncreate table if not exists auth.one_time_tokens (\n id uuid primary key default gen_random_uuid(),\n user_id uuid references auth.users(id) on delete cascade,\n email text,\n phone text,\n token_type text not null, -- otp | magiclink | recovery | sms\n token text not null,\n attempts int not null default 0,\n created_at timestamptz default now(),\n expires_at timestamptz not null,\n constraint one_time_tokens_contact_check check (\n (email is not null and phone is null) or (email is null and phone is not null)\n )\n);\n\n-- Upgrade databases created by Lite <=0.5.9 without touching existing email tokens.\nalter table auth.one_time_tokens add column if not exists phone text;\nalter table auth.one_time_tokens alter column email drop not null;\ndo $phone_otp_contact_constraint$\nbegin\n if not exists (\n select 1 from pg_constraint\n where conrelid = 'auth.one_time_tokens'::regclass\n and conname = 'one_time_tokens_contact_check'\n ) then\n alter table auth.one_time_tokens add constraint one_time_tokens_contact_check check (\n (email is not null and phone is null) or (email is null and phone is not null)\n );\n end if;\nend $phone_otp_contact_constraint$;\n\ncreate unique index if not exists one_time_tokens_phone_type_idx\n on auth.one_time_tokens(phone, token_type) where phone is not null;\n\n-- Only a keyed phone fingerprint is persisted for cooldown enforcement; the\n-- normalized phone number never enters this table.\ncreate table if not exists auth.phone_otp_cooldowns (\n phone_fingerprint text primary key,\n issuance_id uuid not null,\n last_sent_at timestamptz not null default now()\n);\n\ncreate table if not exists auth.identities (\n id uuid primary key default gen_random_uuid(),\n user_id uuid not null references auth.users(id) on delete cascade,\n provider text not null,\n provider_id text not null,\n identity_data jsonb default '{}'::jsonb,\n last_sign_in_at timestamptz default now(),\n created_at timestamptz default now(),\n updated_at timestamptz default now(),\n unique (provider, provider_id)\n);\n\n-- OAuth / PKCE flow state, bridging /authorize \u2192 provider \u2192 /callback \u2192 exchange\ncreate table if not exists auth.flow_state (\n id uuid primary key default gen_random_uuid(),\n provider text not null,\n provider_state text not null unique,\n redirect_to text,\n code_challenge text,\n code_challenge_method text,\n auth_code text unique,\n user_id uuid references auth.users(id) on delete cascade,\n created_at timestamptz default now(),\n expires_at timestamptz not null\n);\n\ncreate table if not exists auth.mfa_factors (\n id uuid primary key default gen_random_uuid(),\n user_id uuid not null references auth.users(id) on delete cascade,\n friendly_name text,\n factor_type text not null default 'totp',\n status text not null default 'unverified', -- unverified | verified\n secret text,\n created_at timestamptz default now(),\n updated_at timestamptz default now()\n);\n\ncreate table if not exists auth.mfa_challenges (\n id uuid primary key default gen_random_uuid(),\n factor_id uuid not null references auth.mfa_factors(id) on delete cascade,\n verified_at timestamptz,\n created_at timestamptz default now(),\n expires_at timestamptz not null\n);\n\n-- append-only security audit trail (GoTrue-compatible shape)\ncreate table if not exists auth.audit_log_entries (\n instance_id uuid default '00000000-0000-0000-0000-000000000000',\n id uuid primary key default gen_random_uuid(),\n payload jsonb,\n created_at timestamptz default now(),\n ip_address varchar(64) not null default ''\n);\ncreate index if not exists audit_logs_instance_id_idx on auth.audit_log_entries(created_at);\n\n-- runtime-mutable instance settings (Studio auth toggles)\ncreate table if not exists auth.config (\n key text primary key,\n value jsonb not null,\n updated_at timestamptz default now()\n);\n\ngrant usage on schema auth to anon, authenticated, service_role;\n\ncreate or replace function auth.jwt() returns jsonb\nlanguage sql stable as $$\n select coalesce(\n nullif(current_setting('request.jwt.claims', true), ''),\n '{}'\n )::jsonb\n$$;\n\ncreate or replace function auth.uid() returns uuid\nlanguage sql stable as $$\n select nullif(auth.jwt() ->> 'sub', '')::uuid\n$$;\n\ncreate or replace function auth.role() returns text\nlanguage sql stable as $$\n select coalesce(auth.jwt() ->> 'role', 'anon')\n$$;\n\ncreate or replace function auth.email() returns text\nlanguage sql stable as $$\n select auth.jwt() ->> 'email'\n$$;\n\ngrant execute on function auth.jwt(), auth.uid(), auth.role(), auth.email()\n to anon, authenticated, service_role;\n\n-- \u2500\u2500 Storage schema (storage-api-compatible subset) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ncreate schema if not exists storage;\n\ncreate table if not exists storage.buckets (\n id text primary key,\n name text not null unique,\n owner uuid,\n owner_id text,\n public boolean default false,\n file_size_limit bigint,\n allowed_mime_types text[],\n created_at timestamptz default now(),\n updated_at timestamptz default now()\n);\n\nalter table storage.buckets add column if not exists owner_id text;\nupdate storage.buckets\nset owner_id = owner::text\nwhere owner_id is null and owner is not null;\n\ncreate table if not exists storage.objects (\n id uuid primary key default gen_random_uuid(),\n bucket_id text not null references storage.buckets(id),\n name text not null,\n owner uuid,\n owner_id text,\n version text,\n metadata jsonb default '{}'::jsonb,\n created_at timestamptz default now(),\n updated_at timestamptz default now(),\n last_accessed_at timestamptz default now(),\n unique (bucket_id, name)\n);\n\n-- storage-api keeps the legacy UUID owner and the current text owner_id in\n-- parallel. Re-running bootstrap upgrades existing Lite databases and retains\n-- object ownership for rows created before owner_id support was added.\nalter table storage.objects add column if not exists owner_id text;\nupdate storage.objects\nset owner_id = owner::text\nwhere owner_id is null and owner is not null;\n\ncreate index if not exists objects_bucket_name_idx on storage.objects(bucket_id, name);\n\ngrant usage on schema storage to anon, authenticated, service_role;\ngrant select on storage.buckets to anon, authenticated, service_role;\ngrant all on storage.buckets to service_role;\ngrant all on storage.objects to anon, authenticated, service_role;\n\ncreate or replace function storage.foldername(name text) returns text[]\nlanguage sql immutable as $$\n select (string_to_array(name, '/'))[1 : array_length(string_to_array(name, '/'), 1) - 1]\n$$;\n\ncreate or replace function storage.filename(name text) returns text\nlanguage sql immutable as $$\n select (string_to_array(name, '/'))[array_length(string_to_array(name, '/'), 1)]\n$$;\n\ncreate or replace function storage.extension(name text) returns text\nlanguage sql immutable as $$\n select reverse(split_part(reverse(storage.filename(name)), '.', 1))\n$$;\n\nalter table storage.objects enable row level security;\n\ndrop policy if exists supacloud_lite_authenticated_all on storage.objects;\ndrop policy if exists supacloud_lite_public_read on storage.objects;\n\ndo $legacy_policy_cleanup$\ndeclare\n legacy_prefix constant text := 'tin' || 'base';\nbegin\n execute format(\n 'drop policy if exists %I on storage.objects',\n legacy_prefix || '_authenticated_all'\n );\n execute format(\n 'drop policy if exists %I on storage.objects',\n legacy_prefix || '_public_read'\n );\nend $legacy_policy_cleanup$;\n\n-- \u2500\u2500 Migration bookkeeping (same table the Supabase CLI uses) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ncreate schema if not exists supabase_migrations;\n\ncreate table if not exists supabase_migrations.schema_migrations (\n version text primary key,\n name text,\n statements text[],\n applied_at timestamptz default now()\n);\n\ncreate table if not exists supabase_migrations.seed_files (\n path text primary key,\n hash text,\n applied_at timestamptz default now()\n);\n\n-- \u2500\u2500 Realtime CDC plumbing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndo $legacy_schema_upgrade$\ndeclare\n legacy_schema constant text := 'tin' || 'base';\nbegin\n if exists (select 1 from pg_namespace where nspname = legacy_schema)\n and not exists (select 1 from pg_namespace where nspname = 'supacloud_lite') then\n execute format('alter schema %I rename to supacloud_lite', legacy_schema);\n end if;\nend $legacy_schema_upgrade$;\n\ncreate schema if not exists supacloud_lite;\n\ncreate or replace function supacloud_lite.cdc_notify() returns trigger\nlanguage plpgsql security definer as $$\ndeclare\n payload text;\nbegin\n payload := json_build_object(\n 'schema', TG_TABLE_SCHEMA,\n 'table', TG_TABLE_NAME,\n 'type', TG_OP,\n 'commit_timestamp', to_char(now() at time zone 'utc', 'YYYY-MM-DD\"T\"HH24:MI:SS.MS\"Z\"'),\n 'record', case when TG_OP in ('INSERT', 'UPDATE') then row_to_json(NEW) else null end,\n 'old_record', case when TG_OP in ('UPDATE', 'DELETE') then row_to_json(OLD) else null end\n )::text;\n -- pg_notify payloads are capped at ~8kB; degrade like Supabase does.\n if octet_length(payload) > 7500 then\n payload := json_build_object(\n 'schema', TG_TABLE_SCHEMA,\n 'table', TG_TABLE_NAME,\n 'type', TG_OP,\n 'commit_timestamp', to_char(now() at time zone 'utc', 'YYYY-MM-DD\"T\"HH24:MI:SS.MS\"Z\"'),\n 'record', null,\n 'old_record', null,\n 'errors', json_build_array('Payload too large')\n )::text;\n end if;\n perform pg_notify('supacloud_lite_cdc', payload);\n return coalesce(NEW, OLD);\nend $$;\n\ndo $legacy_trigger_upgrade$\ndeclare\n legacy_schema constant text := 'tin' || 'base';\n legacy_trigger constant text := ('tin' || 'base') || '_cdc';\n trigger_record record;\n target_exists boolean;\nbegin\n for trigger_record in\n select c.oid as relation_oid, n.nspname as schema_name, c.relname as table_name\n from pg_trigger tg\n join pg_class c on c.oid = tg.tgrelid\n join pg_namespace n on n.oid = c.relnamespace\n join pg_proc p on p.oid = tg.tgfoid\n join pg_namespace pn on pn.oid = p.pronamespace\n where not tg.tgisinternal\n and tg.tgname = legacy_trigger\n and p.proname = 'cdc_notify'\n and pn.nspname in (legacy_schema, 'supacloud_lite')\n loop\n select exists (\n select 1\n from pg_trigger\n where not tgisinternal\n and tgrelid = trigger_record.relation_oid\n and tgname = 'supacloud_lite_cdc'\n ) into target_exists;\n\n execute format(\n 'drop trigger %I on %I.%I',\n legacy_trigger,\n trigger_record.schema_name,\n trigger_record.table_name\n );\n\n if not target_exists then\n execute format(\n 'create trigger supacloud_lite_cdc after insert or update or delete on %I.%I for each row execute function supacloud_lite.cdc_notify()',\n trigger_record.schema_name,\n trigger_record.table_name\n );\n end if;\n end loop;\nend $legacy_trigger_upgrade$;\n\ndo $legacy_schema_cleanup$\ndeclare\n legacy_schema constant text := 'tin' || 'base';\nbegin\n if exists (\n select 1\n from pg_proc p\n join pg_namespace n on n.oid = p.pronamespace\n where n.nspname = legacy_schema\n and p.proname = 'cdc_notify'\n and p.pronargs = 0\n ) then\n execute format('drop function %I.cdc_notify()', legacy_schema);\n end if;\n\n if exists (select 1 from pg_namespace where nspname = legacy_schema) then\n begin\n execute format('drop schema %I', legacy_schema);\n exception when dependent_objects_still_exist then\n raise notice 'SupaCloud Lite: retained non-empty legacy runtime schema';\n end;\n end if;\nend $legacy_schema_cleanup$;\n\n-- \u2500\u2500 Realtime Authorization + broadcast-from-database \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n-- Mirrors Supabase Realtime: developers write RLS policies on\n-- realtime.messages using realtime.topic(); a SELECT policy grants a private\n-- channel's subscribers the right to *receive*, an INSERT policy the right to\n-- *broadcast*. realtime.send() lets the database push a broadcast to a topic.\ncreate schema if not exists realtime;\n\ncreate table if not exists realtime.messages (\n id uuid not null default gen_random_uuid(),\n topic text not null,\n extension text not null,\n event text,\n payload jsonb,\n private boolean default false,\n inserted_at timestamptz not null default now(),\n updated_at timestamptz not null default now(),\n primary key (id)\n);\nalter table realtime.messages enable row level security;\n\ngrant usage on schema realtime to anon, authenticated, service_role;\ngrant all on realtime.messages to anon, authenticated, service_role;\n\n-- the topic currently being authorized/broadcast (set per operation)\ncreate or replace function realtime.topic() returns text\n language sql stable as $$ select nullif(current_setting('realtime.topic', true), '') $$;\n\n-- push a broadcast message to all subscribers of <topic> from SQL / triggers.\n-- Delivered over the websocket by the in-process realtime engine, which listens\n-- on the 'supacloud_lite_realtime_broadcast' channel.\ncreate or replace function realtime.send(payload jsonb, event text, topic text, private boolean default true)\n returns void language plpgsql security definer as $$\nbegin\n perform pg_notify(\n 'supacloud_lite_realtime_broadcast',\n json_build_object('topic', topic, 'event', event, 'payload', payload, 'private', private)::text\n );\nexception when others then\n -- never let a notify failure abort the caller's transaction\n null;\nend $$;\n\ngrant execute on function realtime.topic() to anon, authenticated, service_role;\ngrant execute on function realtime.send(jsonb, text, text, boolean) to anon, authenticated, service_role;\n";
|
|
21
21
|
//# sourceMappingURL=bootstrap.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../../src/runtime/db/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,66KA+KjC,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../../src/runtime/db/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,66KA+KjC,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,aAAa,0opBAshBzB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../../src/runtime/db/database.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../../src/runtime/db/database.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Compatibility SQL for extensions that are not available in every engine.
|
|
3
|
+
* PGlite installs the official pgmq extension by default; the complete pgmq
|
|
4
|
+
* SQL fallback remains for native PostgreSQL installations without pgmq.
|
|
5
|
+
* The pgmq_public façade is Lite-owned in either mode. cron records jobs here;
|
|
6
|
+
* the in-process CronService (src/cron/service.ts) executes them.
|
|
6
7
|
*
|
|
7
8
|
* These match the real extensions' function signatures closely enough that
|
|
8
9
|
* `pgmq.send(...)`, `pgmq.read(...)`, `select cron.schedule(...)` etc. behave
|
|
9
10
|
* the same from the client's and a migration's point of view.
|
|
10
11
|
*/
|
|
11
12
|
/**
|
|
12
|
-
* Self-contained SQL
|
|
13
|
+
* Self-contained SQL fallback for the pgmq extension: per-queue `q_<name>` /
|
|
13
14
|
* `a_<name>` tables plus create/send/read/pop/delete/archive/etc functions,
|
|
14
15
|
* matching pgmq's signatures. No C extension, no background worker needed.
|
|
15
16
|
*/
|
|
16
|
-
export declare const PGMQ_SQL = "\ncreate schema if not exists pgmq;\nrevoke all on schema pgmq from public, anon, authenticated;\ngrant usage on schema pgmq to service_role;\n\ncreate table if not exists pgmq.meta (\n queue_name text primary key,\n physical_name text not null unique,\n is_partitioned boolean not null default false,\n is_unlogged boolean not null default false,\n created_at timestamptz not null default now()\n);\n\n-- Preserve queues created by older Lite versions. Their physical names were\n-- the queue names, so only the already-truncated identifier can be recovered.\ninsert into pgmq.meta (queue_name, physical_name)\nselect substring(tablename from 3), substring(tablename from 3)\nfrom pg_tables\nwhere schemaname = 'pgmq' and left(tablename, 2) = 'q_'\non conflict do nothing;\n\ndo $$ begin\n if not exists (select 1 from pg_type t join pg_namespace n on n.oid = t.typnamespace\n where t.typname = 'message_record' and n.nspname = 'pgmq') then\n create type pgmq.message_record as (\n msg_id bigint, read_ct integer, enqueued_at timestamptz, vt timestamptz, message jsonb\n );\n end if;\nend $$;\n\ncreate or replace function pgmq._normalize_queue_name(queue_name text)\nreturns text language plpgsql immutable set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare normalized text := btrim(queue_name);\nbegin\n if normalized is null or normalized !~ '^[a-z0-9][a-z0-9_-]{0,127}$' then\n raise exception 'invalid queue name: must match ^[a-z0-9][a-z0-9_-]{0,127}$'\n using errcode = '22023';\n end if;\n return normalized;\nend $pgmq$;\n\n-- PostgreSQL identifiers are limited to 63 bytes. Keep the conventional table\n-- name for short queues and add a deterministic hash for valid 62-128 byte\n-- public names so truncation can never merge two queues.\ncreate or replace function pgmq._physical_queue_name(queue_name text)\nreturns text language sql immutable set search_path = pgmq, pg_catalog, public as $pgmq$\n select case when length(normalized) <= 61 then normalized\n else left(normalized, 28) || '_' || md5(normalized) end\n from (select pgmq._normalize_queue_name(queue_name) as normalized) names;\n$pgmq$;\n\ncreate or replace function pgmq._resolve_queue(queue_name text)\nreturns text language plpgsql stable security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare normalized text := pgmq._normalize_queue_name(queue_name); physical text;\nbegin\n select physical_name into physical from pgmq.meta where pgmq.meta.queue_name = normalized;\n if physical is null then\n raise exception 'queue \"%\" does not exist', normalized using errcode = '42P01';\n end if;\n return physical;\nend $pgmq$;\n\ncreate or replace function pgmq.create(queue_name text) returns void language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare normalized text := pgmq._normalize_queue_name(queue_name); physical text := pgmq._physical_queue_name(queue_name);\nbegin\n insert into pgmq.meta (queue_name, physical_name) values (normalized, physical)\n on conflict do nothing;\n select physical_name into physical from pgmq.meta where pgmq.meta.queue_name = normalized;\n execute format('create table if not exists pgmq.%I (\n msg_id bigint generated always as identity primary key,\n read_ct integer not null default 0,\n enqueued_at timestamptz not null default now(),\n vt timestamptz not null default now(),\n message jsonb)', 'q_' || physical);\n execute format('create table if not exists pgmq.%I (\n msg_id bigint primary key, read_ct integer not null default 0,\n enqueued_at timestamptz not null, archived_at timestamptz not null default now(),\n vt timestamptz, message jsonb)', 'a_' || physical);\nend $pgmq$;\n\ncreate or replace function pgmq.send(queue_name text, msg jsonb, delay integer default 0)\nreturns bigint language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare id bigint; physical text := pgmq._resolve_queue(queue_name);\nbegin\n execute format('insert into pgmq.%I (vt, message) values (now() + make_interval(secs => $1), $2) returning msg_id', 'q_' || physical)\n into id using delay, msg;\n return id;\nend $pgmq$;\n\ncreate or replace function pgmq.send_batch(queue_name text, msgs jsonb[], delay integer default 0)\nreturns setof bigint language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare queue_message jsonb;\nbegin\n perform pgmq._resolve_queue(queue_name);\n foreach queue_message in array msgs loop\n return next pgmq.send(queue_name, queue_message, delay);\n end loop;\nend $pgmq$;\n\ncreate or replace function pgmq.read(queue_name text, vt integer, qty integer)\nreturns setof pgmq.message_record language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare physical text := pgmq._resolve_queue(queue_name);\nbegin\n return query execute format($fmt$\n with cte as (\n select msg_id from pgmq.%I where vt <= now() order by msg_id limit $1 for update skip locked\n )\n update pgmq.%I m set vt = now() + make_interval(secs => $2), read_ct = read_ct + 1\n from cte where m.msg_id = cte.msg_id\n returning m.msg_id, m.read_ct, m.enqueued_at, m.vt, m.message\n $fmt$, 'q_' || physical, 'q_' || physical) using qty, vt;\nend $pgmq$;\n\ncreate or replace function pgmq.pop(queue_name text)\nreturns setof pgmq.message_record language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare physical text := pgmq._resolve_queue(queue_name);\nbegin\n return query execute format($fmt$\n with cte as (select msg_id from pgmq.%I where vt <= now() order by msg_id limit 1 for update skip locked)\n delete from pgmq.%I m using cte where m.msg_id = cte.msg_id\n returning m.msg_id, m.read_ct, m.enqueued_at, m.vt, m.message\n $fmt$, 'q_' || physical, 'q_' || physical);\nend $pgmq$;\n\ncreate or replace function pgmq.set_vt(queue_name text, msg_id bigint, vt_offset integer)\nreturns setof pgmq.message_record language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare physical text := pgmq._resolve_queue(queue_name);\nbegin\n return query execute format($fmt$\n update pgmq.%I m set vt = now() + make_interval(secs => $1)\n where m.msg_id = $2\n returning m.msg_id, m.read_ct, m.enqueued_at, m.vt, m.message\n $fmt$, 'q_' || physical) using vt_offset, msg_id;\nend $pgmq$;\n\ncreate or replace function pgmq.delete(queue_name text, msg_id bigint)\nreturns boolean language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare n integer; physical text := pgmq._resolve_queue(queue_name);\nbegin\n execute format('delete from pgmq.%I where msg_id = $1', 'q_' || physical) using msg_id;\n get diagnostics n = row_count;\n return n > 0;\nend $pgmq$;\n\ncreate or replace function pgmq.archive(queue_name text, msg_id bigint)\nreturns boolean language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare n integer; physical text := pgmq._resolve_queue(queue_name);\nbegin\n execute format($fmt$\n with del as (delete from pgmq.%I where msg_id = $1 returning *)\n insert into pgmq.%I (msg_id, read_ct, enqueued_at, vt, message)\n select msg_id, read_ct, enqueued_at, vt, message from del\n $fmt$, 'q_' || physical, 'a_' || physical) using msg_id;\n get diagnostics n = row_count;\n return n > 0;\nend $pgmq$;\n\ncreate or replace function pgmq.drop_queue(queue_name text) returns boolean language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare normalized text := pgmq._normalize_queue_name(queue_name); physical text;\nbegin\n select physical_name into physical from pgmq.meta where pgmq.meta.queue_name = normalized;\n if physical is null then return false; end if;\n execute format('drop table if exists pgmq.%I', 'q_' || physical);\n execute format('drop table if exists pgmq.%I', 'a_' || physical);\n delete from pgmq.meta where pgmq.meta.queue_name = normalized;\n return true;\nend $pgmq$;\n\ncreate or replace function pgmq.purge_queue(queue_name text) returns bigint language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare n bigint; physical text := pgmq._resolve_queue(queue_name);\nbegin\n execute format('delete from pgmq.%I', 'q_' || physical);\n get diagnostics n = row_count;\n return n;\nend $pgmq$;\n\ncreate or replace function pgmq.list_queues()\nreturns table(queue_name text, is_partitioned boolean, is_unlogged boolean, created_at timestamptz)\nlanguage sql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\n select queue_name, is_partitioned, is_unlogged, created_at from pgmq.meta;\n$pgmq$;\n\nrevoke all on all functions in schema pgmq from public, anon, authenticated;\ngrant execute on all functions in schema pgmq to service_role;\n\n-- supacloud:sql-module:pgmq-public:start\nDO $pgmq_extension$\nBEGIN\n IF to_regprocedure('pgmq.send(text,jsonb,integer)') IS NULL THEN\n EXECUTE 'CREATE EXTENSION IF NOT EXISTS pgmq';\n END IF;\nEND\n$pgmq_extension$;\n\nCREATE SCHEMA IF NOT EXISTS pgmq_public;\nGRANT USAGE ON SCHEMA pgmq_public TO anon, authenticated, service_role;\nCREATE SCHEMA IF NOT EXISTS supacloud_queue;\nCREATE TABLE IF NOT EXISTS supacloud_queue.job_keys (\n queue_name text NOT NULL,\n job_key text NOT NULL,\n msg_id bigint NOT NULL,\n message jsonb NOT NULL,\n sleep_seconds integer NOT NULL,\n created_at timestamptz NOT NULL DEFAULT clock_timestamp(),\n PRIMARY KEY (queue_name, job_key)\n);\n-- Upgrade early key-only installations without inventing an input identity.\nALTER TABLE supacloud_queue.job_keys ADD COLUMN IF NOT EXISTS message jsonb;\nALTER TABLE supacloud_queue.job_keys ADD COLUMN IF NOT EXISTS sleep_seconds integer;\nREVOKE ALL ON SCHEMA supacloud_queue FROM PUBLIC, anon, authenticated, service_role;\nREVOKE ALL ON TABLE supacloud_queue.job_keys FROM PUBLIC, anon, authenticated, service_role;\n\nCREATE OR REPLACE FUNCTION pgmq_public.require_public_queue(queue_name text)\nRETURNS text\nLANGUAGE plpgsql\nIMMUTABLE\nSET search_path = ''\nAS $$\nDECLARE\n normalized_queue_name text := lower(btrim(queue_name));\nBEGIN\n IF left(normalized_queue_name, char_length('supacloud_internal_')) = 'supacloud_internal_' THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_NAME_RESERVED' USING ERRCODE = '42501';\n END IF;\n IF queue_name IS NULL OR queue_name !~ '^[a-z0-9][a-z0-9_-]{0,127}$' THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_NAME_INVALID' USING ERRCODE = '22023';\n END IF;\n RETURN queue_name;\nEND;\n$$;\n\n-- Return-type changes require replacement. Do not CASCADE through user dependencies.\nDO $pgmq_receipt_types$\nBEGIN\n IF EXISTS (\n SELECT 1 FROM pg_catalog.pg_proc\n WHERE oid = to_regprocedure('pgmq_public.send(text,jsonb,integer)')\n AND prorettype = 'bigint'::regtype\n ) THEN\n DROP FUNCTION pgmq_public.send(text,jsonb,integer);\n END IF;\n IF EXISTS (\n SELECT 1 FROM pg_catalog.pg_proc\n WHERE oid = to_regprocedure('pgmq_public.send_batch(text,jsonb[],integer)')\n AND prorettype = 'bigint'::regtype\n ) THEN\n DROP FUNCTION pgmq_public.send_batch(text,jsonb[],integer);\n END IF;\n IF EXISTS (\n SELECT 1 FROM pg_catalog.pg_proc\n WHERE oid = to_regprocedure('pgmq_public.read(text,integer,integer)')\n AND prorettype = 'pgmq.message_record'::regtype\n ) THEN\n DROP FUNCTION pgmq_public.read(text,integer,integer);\n END IF;\n IF EXISTS (\n SELECT 1 FROM pg_catalog.pg_proc\n WHERE oid = to_regprocedure('pgmq_public.pop(text)')\n AND prorettype = 'pgmq.message_record'::regtype\n ) THEN\n DROP FUNCTION pgmq_public.pop(text);\n END IF;\nEND\n$pgmq_receipt_types$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.require_seconds(value integer)\nRETURNS integer LANGUAGE plpgsql IMMUTABLE SET search_path = ''\nAS $$\nBEGIN\n IF value IS NULL OR value < 0 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_SECONDS_INVALID' USING ERRCODE = '22023';\n END IF;\n RETURN value;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.require_read_count(value integer)\nRETURNS integer LANGUAGE plpgsql IMMUTABLE SET search_path = ''\nAS $$\nBEGIN\n IF value IS NULL OR value < 1 OR value > 10000 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_COUNT_INVALID' USING ERRCODE = '22023';\n END IF;\n RETURN value;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.require_message_id(value bigint)\nRETURNS bigint LANGUAGE plpgsql IMMUTABLE SET search_path = ''\nAS $$\nBEGIN\n IF value IS NULL OR value < 1 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_MESSAGE_ID_INVALID' USING ERRCODE = '22023';\n END IF;\n RETURN value;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.message_node_count(value jsonb)\nRETURNS integer LANGUAGE plpgsql IMMUTABLE SET search_path = ''\nAS $$\nDECLARE\n node_count integer;\n maximum_depth integer;\nBEGIN\n IF value IS NULL THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_MESSAGE_INVALID' USING ERRCODE = '22023';\n END IF;\n IF octet_length(convert_to(value::text, 'UTF8')) > 1048576 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_PAYLOAD_TOO_LARGE' USING ERRCODE = '22023';\n END IF;\n WITH RECURSIVE tree(item, depth) AS (\n SELECT value, 0\n UNION ALL\n SELECT child.item, tree.depth + 1\n FROM tree\n CROSS JOIN LATERAL (\n SELECT element AS item\n FROM jsonb_array_elements(CASE WHEN jsonb_typeof(tree.item) = 'array' THEN tree.item ELSE '[]'::jsonb END) AS element\n UNION ALL\n SELECT entry.value AS item\n FROM jsonb_each(CASE WHEN jsonb_typeof(tree.item) = 'object' THEN tree.item ELSE '{}'::jsonb END) AS entry\n ) AS child\n WHERE tree.depth < 65\n )\n SELECT count(*)::integer, max(depth) INTO node_count, maximum_depth\n FROM (SELECT depth FROM tree LIMIT 10001) AS bounded;\n IF node_count > 10000 OR maximum_depth > 64 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_STRUCTURE_TOO_LARGE' USING ERRCODE = '22023';\n END IF;\n RETURN node_count;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.require_message(value jsonb)\nRETURNS jsonb LANGUAGE plpgsql IMMUTABLE SET search_path = ''\nAS $$\nBEGIN\n PERFORM pgmq_public.message_node_count(value);\n RETURN value;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.require_messages(values_ jsonb[])\nRETURNS jsonb[] LANGUAGE plpgsql IMMUTABLE SET search_path = ''\nAS $$\nDECLARE\n item jsonb;\n total_bytes bigint := 0;\n total_nodes integer := 0;\nBEGIN\n IF values_ IS NULL OR cardinality(values_) < 1 OR cardinality(values_) > 10000\n OR array_ndims(values_) <> 1 OR array_lower(values_, 1) <> 1 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_BATCH_INVALID' USING ERRCODE = '22023';\n END IF;\n FOREACH item IN ARRAY values_ LOOP\n total_nodes := total_nodes + pgmq_public.message_node_count(item);\n IF total_nodes > 100000 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_STRUCTURE_TOO_LARGE' USING ERRCODE = '22023';\n END IF;\n total_bytes := total_bytes + octet_length(convert_to(item::text, 'UTF8'));\n IF total_bytes > 8388608 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_PAYLOAD_TOO_LARGE' USING ERRCODE = '22023';\n END IF;\n END LOOP;\n RETURN values_;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.send(queue_name text, message jsonb, sleep_seconds integer DEFAULT 0)\nRETURNS SETOF text\nLANGUAGE sql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$ 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); $$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.send_batch(queue_name text, messages jsonb[], sleep_seconds integer DEFAULT 0)\nRETURNS SETOF text\nLANGUAGE sql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$ 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); $$;\n\n-- Additive SupaCloud extension; official send/read/archive/delete contracts stay unchanged.\nCREATE OR REPLACE FUNCTION pgmq_public.send_idempotent(\n queue_name text, message jsonb, p_job_key text, sleep_seconds integer DEFAULT 0\n)\nRETURNS TABLE (msg_id text, created boolean)\nLANGUAGE plpgsql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$\nDECLARE\n normalized_queue text := pgmq_public.require_public_queue(queue_name);\n captured_message jsonb := pgmq_public.require_message(message);\n captured_delay integer := pgmq_public.require_seconds(sleep_seconds);\n inserted boolean;\n sent_id bigint;\n existing_id bigint;\n existing_message jsonb;\n existing_delay integer;\nBEGIN\n IF p_job_key IS NULL OR p_job_key !~ '^[A-Za-z0-9_.:@/-]{1,200}$' THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_JOB_KEY_INVALID' USING ERRCODE = '22023';\n END IF;\n INSERT INTO supacloud_queue.job_keys(queue_name, job_key, msg_id, message, sleep_seconds)\n VALUES (normalized_queue, p_job_key, 0, captured_message, captured_delay)\n ON CONFLICT ON CONSTRAINT job_keys_pkey DO NOTHING;\n inserted := FOUND;\n IF NOT inserted THEN\n SELECT keys.msg_id, keys.message, keys.sleep_seconds\n INTO existing_id, existing_message, existing_delay\n FROM supacloud_queue.job_keys AS keys\n WHERE keys.queue_name = normalized_queue AND keys.job_key = p_job_key;\n IF existing_id IS NULL OR existing_id < 1 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_JOB_KEY_UNAVAILABLE' USING ERRCODE = '55000';\n END IF;\n IF existing_message IS DISTINCT FROM captured_message OR existing_delay IS DISTINCT FROM captured_delay THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_JOB_KEY_CONFLICT' USING ERRCODE = '22023';\n END IF;\n -- This is a durable enqueue receipt, not evidence that the message is still pending.\n RETURN QUERY SELECT existing_id::text, false;\n RETURN;\n END IF;\n SELECT receipt.msg_id INTO STRICT sent_id\n FROM pgmq.send(normalized_queue, captured_message, captured_delay) AS receipt(msg_id);\n PERFORM pgmq_public.require_message_id(sent_id);\n UPDATE supacloud_queue.job_keys AS keys SET msg_id = sent_id\n WHERE keys.queue_name = normalized_queue AND keys.job_key = p_job_key;\n RETURN QUERY SELECT sent_id::text, true;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.read(queue_name text, sleep_seconds integer, n integer)\nRETURNS TABLE (\n msg_id text, read_ct integer, enqueued_at timestamptz,\n last_read_at timestamptz, vt timestamptz, message jsonb, headers jsonb\n)\nLANGUAGE sql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$\n SELECT receipt.msg_id::text, receipt.read_ct, receipt.enqueued_at,\n (to_jsonb(receipt)->>'last_read_at')::timestamptz, receipt.vt,\n receipt.message, to_jsonb(receipt)->'headers'\n FROM pgmq.read(pgmq_public.require_public_queue(queue_name),\n pgmq_public.require_seconds(sleep_seconds), pgmq_public.require_read_count(n)) AS receipt;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.pop(queue_name text)\nRETURNS TABLE (\n msg_id text, read_ct integer, enqueued_at timestamptz,\n last_read_at timestamptz, vt timestamptz, message jsonb, headers jsonb\n)\nLANGUAGE sql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$\n SELECT receipt.msg_id::text, receipt.read_ct, receipt.enqueued_at,\n (to_jsonb(receipt)->>'last_read_at')::timestamptz, receipt.vt,\n receipt.message, to_jsonb(receipt)->'headers'\n FROM pgmq.pop(pgmq_public.require_public_queue(queue_name)) AS receipt;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.archive(queue_name text, message_id bigint)\nRETURNS boolean\nLANGUAGE sql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$ SELECT pgmq.archive(pgmq_public.require_public_queue(queue_name), pgmq_public.require_message_id(message_id)); $$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.\"delete\"(queue_name text, message_id bigint)\nRETURNS boolean\nLANGUAGE sql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$ SELECT pgmq.delete(pgmq_public.require_public_queue(queue_name), pgmq_public.require_message_id(message_id)); $$;\n\nREVOKE ALL ON ALL FUNCTIONS IN SCHEMA pgmq_public FROM PUBLIC;\nGRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA pgmq_public TO anon, authenticated, service_role;\n\nNOTIFY pgrst, 'reload schema';\n-- supacloud:sql-module:pgmq-public:end\n";
|
|
17
|
+
export declare const PGMQ_CORE_SQL = "\ncreate schema if not exists pgmq;\nrevoke all on schema pgmq from public, anon, authenticated;\ngrant usage on schema pgmq to service_role;\n\ncreate table if not exists pgmq.meta (\n queue_name text primary key,\n physical_name text not null unique,\n is_partitioned boolean not null default false,\n is_unlogged boolean not null default false,\n created_at timestamptz not null default now()\n);\n\n-- Preserve queues created by older Lite versions. Their physical names were\n-- the queue names, so only the already-truncated identifier can be recovered.\ninsert into pgmq.meta (queue_name, physical_name)\nselect substring(tablename from 3), substring(tablename from 3)\nfrom pg_tables\nwhere schemaname = 'pgmq' and left(tablename, 2) = 'q_'\non conflict do nothing;\n\ndo $$ begin\n if not exists (select 1 from pg_type t join pg_namespace n on n.oid = t.typnamespace\n where t.typname = 'message_record' and n.nspname = 'pgmq') then\n create type pgmq.message_record as (\n msg_id bigint, read_ct integer, enqueued_at timestamptz, vt timestamptz, message jsonb\n );\n end if;\nend $$;\n\ncreate or replace function pgmq._normalize_queue_name(queue_name text)\nreturns text language plpgsql immutable set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare normalized text := btrim(queue_name);\nbegin\n if normalized is null or normalized !~ '^[a-z0-9][a-z0-9_-]{0,127}$' then\n raise exception 'invalid queue name: must match ^[a-z0-9][a-z0-9_-]{0,127}$'\n using errcode = '22023';\n end if;\n return normalized;\nend $pgmq$;\n\n-- PostgreSQL identifiers are limited to 63 bytes. Keep the conventional table\n-- name for short queues and add a deterministic hash for valid 62-128 byte\n-- public names so truncation can never merge two queues.\ncreate or replace function pgmq._physical_queue_name(queue_name text)\nreturns text language sql immutable set search_path = pgmq, pg_catalog, public as $pgmq$\n select case when length(normalized) <= 61 then normalized\n else left(normalized, 28) || '_' || md5(normalized) end\n from (select pgmq._normalize_queue_name(queue_name) as normalized) names;\n$pgmq$;\n\ncreate or replace function pgmq._resolve_queue(queue_name text)\nreturns text language plpgsql stable security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare normalized text := pgmq._normalize_queue_name(queue_name); physical text;\nbegin\n select physical_name into physical from pgmq.meta where pgmq.meta.queue_name = normalized;\n if physical is null then\n raise exception 'queue \"%\" does not exist', normalized using errcode = '42P01';\n end if;\n return physical;\nend $pgmq$;\n\ncreate or replace function pgmq.create(queue_name text) returns void language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare normalized text := pgmq._normalize_queue_name(queue_name); physical text := pgmq._physical_queue_name(queue_name);\nbegin\n insert into pgmq.meta (queue_name, physical_name) values (normalized, physical)\n on conflict do nothing;\n select physical_name into physical from pgmq.meta where pgmq.meta.queue_name = normalized;\n execute format('create table if not exists pgmq.%I (\n msg_id bigint generated always as identity primary key,\n read_ct integer not null default 0,\n enqueued_at timestamptz not null default now(),\n vt timestamptz not null default now(),\n message jsonb)', 'q_' || physical);\n execute format('create table if not exists pgmq.%I (\n msg_id bigint primary key, read_ct integer not null default 0,\n enqueued_at timestamptz not null, archived_at timestamptz not null default now(),\n vt timestamptz, message jsonb)', 'a_' || physical);\nend $pgmq$;\n\ncreate or replace function pgmq.send(queue_name text, msg jsonb, delay integer default 0)\nreturns bigint language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare id bigint; physical text := pgmq._resolve_queue(queue_name);\nbegin\n execute format('insert into pgmq.%I (vt, message) values (now() + make_interval(secs => $1), $2) returning msg_id', 'q_' || physical)\n into id using delay, msg;\n return id;\nend $pgmq$;\n\ncreate or replace function pgmq.send_batch(queue_name text, msgs jsonb[], delay integer default 0)\nreturns setof bigint language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare queue_message jsonb;\nbegin\n perform pgmq._resolve_queue(queue_name);\n foreach queue_message in array msgs loop\n return next pgmq.send(queue_name, queue_message, delay);\n end loop;\nend $pgmq$;\n\ncreate or replace function pgmq.read(queue_name text, vt integer, qty integer)\nreturns setof pgmq.message_record language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare physical text := pgmq._resolve_queue(queue_name);\nbegin\n return query execute format($fmt$\n with cte as (\n select msg_id from pgmq.%I where vt <= now() order by msg_id limit $1 for update skip locked\n )\n update pgmq.%I m set vt = now() + make_interval(secs => $2), read_ct = read_ct + 1\n from cte where m.msg_id = cte.msg_id\n returning m.msg_id, m.read_ct, m.enqueued_at, m.vt, m.message\n $fmt$, 'q_' || physical, 'q_' || physical) using qty, vt;\nend $pgmq$;\n\ncreate or replace function pgmq.pop(queue_name text)\nreturns setof pgmq.message_record language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare physical text := pgmq._resolve_queue(queue_name);\nbegin\n return query execute format($fmt$\n with cte as (select msg_id from pgmq.%I where vt <= now() order by msg_id limit 1 for update skip locked)\n delete from pgmq.%I m using cte where m.msg_id = cte.msg_id\n returning m.msg_id, m.read_ct, m.enqueued_at, m.vt, m.message\n $fmt$, 'q_' || physical, 'q_' || physical);\nend $pgmq$;\n\ncreate or replace function pgmq.set_vt(queue_name text, msg_id bigint, vt_offset integer)\nreturns setof pgmq.message_record language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare physical text := pgmq._resolve_queue(queue_name);\nbegin\n return query execute format($fmt$\n update pgmq.%I m set vt = now() + make_interval(secs => $1)\n where m.msg_id = $2\n returning m.msg_id, m.read_ct, m.enqueued_at, m.vt, m.message\n $fmt$, 'q_' || physical) using vt_offset, msg_id;\nend $pgmq$;\n\ncreate or replace function pgmq.delete(queue_name text, msg_id bigint)\nreturns boolean language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare n integer; physical text := pgmq._resolve_queue(queue_name);\nbegin\n execute format('delete from pgmq.%I where msg_id = $1', 'q_' || physical) using msg_id;\n get diagnostics n = row_count;\n return n > 0;\nend $pgmq$;\n\ncreate or replace function pgmq.archive(queue_name text, msg_id bigint)\nreturns boolean language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare n integer; physical text := pgmq._resolve_queue(queue_name);\nbegin\n execute format($fmt$\n with del as (delete from pgmq.%I where msg_id = $1 returning *)\n insert into pgmq.%I (msg_id, read_ct, enqueued_at, vt, message)\n select msg_id, read_ct, enqueued_at, vt, message from del\n $fmt$, 'q_' || physical, 'a_' || physical) using msg_id;\n get diagnostics n = row_count;\n return n > 0;\nend $pgmq$;\n\ncreate or replace function pgmq.drop_queue(queue_name text) returns boolean language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare normalized text := pgmq._normalize_queue_name(queue_name); physical text;\nbegin\n select physical_name into physical from pgmq.meta where pgmq.meta.queue_name = normalized;\n if physical is null then return false; end if;\n execute format('drop table if exists pgmq.%I', 'q_' || physical);\n execute format('drop table if exists pgmq.%I', 'a_' || physical);\n delete from pgmq.meta where pgmq.meta.queue_name = normalized;\n return true;\nend $pgmq$;\n\ncreate or replace function pgmq.purge_queue(queue_name text) returns bigint language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare n bigint; physical text := pgmq._resolve_queue(queue_name);\nbegin\n execute format('delete from pgmq.%I', 'q_' || physical);\n get diagnostics n = row_count;\n return n;\nend $pgmq$;\n\ncreate or replace function pgmq.list_queues()\nreturns table(queue_name text, is_partitioned boolean, is_unlogged boolean, created_at timestamptz)\nlanguage sql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\n select queue_name, is_partitioned, is_unlogged, created_at from pgmq.meta;\n$pgmq$;\n\nrevoke all on all functions in schema pgmq from public, anon, authenticated;\ngrant execute on all functions in schema pgmq to service_role;\n\n";
|
|
18
|
+
/** Lite's public queue façade and idempotent-send support. The real pgmq
|
|
19
|
+
* extension supplies the pgmq schema; this SQL only owns SupaCloud wrappers. */
|
|
20
|
+
export declare const PGMQ_PUBLIC_SQL = "\n-- supacloud:sql-module:pgmq-public:start\nDO $pgmq_extension$\nBEGIN\n IF to_regprocedure('pgmq.send(text,jsonb,integer)') IS NULL THEN\n EXECUTE 'CREATE EXTENSION IF NOT EXISTS pgmq';\n END IF;\nEND\n$pgmq_extension$;\n\nCREATE SCHEMA IF NOT EXISTS pgmq_public;\nGRANT USAGE ON SCHEMA pgmq_public TO anon, authenticated, service_role;\nCREATE SCHEMA IF NOT EXISTS supacloud_queue;\nCREATE TABLE IF NOT EXISTS supacloud_queue.job_keys (\n queue_name text NOT NULL,\n job_key text NOT NULL,\n msg_id bigint NOT NULL,\n message jsonb NOT NULL,\n sleep_seconds integer NOT NULL,\n created_at timestamptz NOT NULL DEFAULT clock_timestamp(),\n PRIMARY KEY (queue_name, job_key)\n);\n-- Upgrade early key-only installations without inventing an input identity.\nALTER TABLE supacloud_queue.job_keys ADD COLUMN IF NOT EXISTS message jsonb;\nALTER TABLE supacloud_queue.job_keys ADD COLUMN IF NOT EXISTS sleep_seconds integer;\nREVOKE ALL ON SCHEMA supacloud_queue FROM PUBLIC, anon, authenticated, service_role;\nREVOKE ALL ON TABLE supacloud_queue.job_keys FROM PUBLIC, anon, authenticated, service_role;\n\nCREATE OR REPLACE FUNCTION pgmq_public.require_public_queue(queue_name text)\nRETURNS text\nLANGUAGE plpgsql\nIMMUTABLE\nSET search_path = ''\nAS $$\nDECLARE\n normalized_queue_name text := lower(btrim(queue_name));\nBEGIN\n IF left(normalized_queue_name, char_length('supacloud_internal_')) = 'supacloud_internal_' THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_NAME_RESERVED' USING ERRCODE = '42501';\n END IF;\n IF queue_name IS NULL OR queue_name !~ '^[a-z0-9][a-z0-9_-]{0,127}$' THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_NAME_INVALID' USING ERRCODE = '22023';\n END IF;\n RETURN queue_name;\nEND;\n$$;\n\n-- Return-type changes require replacement. Do not CASCADE through user dependencies.\nDO $pgmq_receipt_types$\nBEGIN\n IF EXISTS (\n SELECT 1 FROM pg_catalog.pg_proc\n WHERE oid = to_regprocedure('pgmq_public.send(text,jsonb,integer)')\n AND prorettype = 'bigint'::regtype\n ) THEN\n DROP FUNCTION pgmq_public.send(text,jsonb,integer);\n END IF;\n IF EXISTS (\n SELECT 1 FROM pg_catalog.pg_proc\n WHERE oid = to_regprocedure('pgmq_public.send_batch(text,jsonb[],integer)')\n AND prorettype = 'bigint'::regtype\n ) THEN\n DROP FUNCTION pgmq_public.send_batch(text,jsonb[],integer);\n END IF;\n IF EXISTS (\n SELECT 1 FROM pg_catalog.pg_proc\n WHERE oid = to_regprocedure('pgmq_public.read(text,integer,integer)')\n AND prorettype = 'pgmq.message_record'::regtype\n ) THEN\n DROP FUNCTION pgmq_public.read(text,integer,integer);\n END IF;\n IF EXISTS (\n SELECT 1 FROM pg_catalog.pg_proc\n WHERE oid = to_regprocedure('pgmq_public.pop(text)')\n AND prorettype = 'pgmq.message_record'::regtype\n ) THEN\n DROP FUNCTION pgmq_public.pop(text);\n END IF;\nEND\n$pgmq_receipt_types$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.require_seconds(value integer)\nRETURNS integer LANGUAGE plpgsql IMMUTABLE SET search_path = ''\nAS $$\nBEGIN\n IF value IS NULL OR value < 0 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_SECONDS_INVALID' USING ERRCODE = '22023';\n END IF;\n RETURN value;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.require_read_count(value integer)\nRETURNS integer LANGUAGE plpgsql IMMUTABLE SET search_path = ''\nAS $$\nBEGIN\n IF value IS NULL OR value < 1 OR value > 10000 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_COUNT_INVALID' USING ERRCODE = '22023';\n END IF;\n RETURN value;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.require_message_id(value bigint)\nRETURNS bigint LANGUAGE plpgsql IMMUTABLE SET search_path = ''\nAS $$\nBEGIN\n IF value IS NULL OR value < 1 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_MESSAGE_ID_INVALID' USING ERRCODE = '22023';\n END IF;\n RETURN value;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.message_node_count(value jsonb)\nRETURNS integer LANGUAGE plpgsql IMMUTABLE SET search_path = ''\nAS $$\nDECLARE\n node_count integer;\n maximum_depth integer;\nBEGIN\n IF value IS NULL THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_MESSAGE_INVALID' USING ERRCODE = '22023';\n END IF;\n IF octet_length(convert_to(value::text, 'UTF8')) > 1048576 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_PAYLOAD_TOO_LARGE' USING ERRCODE = '22023';\n END IF;\n WITH RECURSIVE tree(item, depth) AS (\n SELECT value, 0\n UNION ALL\n SELECT child.item, tree.depth + 1\n FROM tree\n CROSS JOIN LATERAL (\n SELECT element AS item\n FROM jsonb_array_elements(CASE WHEN jsonb_typeof(tree.item) = 'array' THEN tree.item ELSE '[]'::jsonb END) AS element\n UNION ALL\n SELECT entry.value AS item\n FROM jsonb_each(CASE WHEN jsonb_typeof(tree.item) = 'object' THEN tree.item ELSE '{}'::jsonb END) AS entry\n ) AS child\n WHERE tree.depth < 65\n )\n SELECT count(*)::integer, max(depth) INTO node_count, maximum_depth\n FROM (SELECT depth FROM tree LIMIT 10001) AS bounded;\n IF node_count > 10000 OR maximum_depth > 64 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_STRUCTURE_TOO_LARGE' USING ERRCODE = '22023';\n END IF;\n RETURN node_count;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.require_message(value jsonb)\nRETURNS jsonb LANGUAGE plpgsql IMMUTABLE SET search_path = ''\nAS $$\nBEGIN\n PERFORM pgmq_public.message_node_count(value);\n RETURN value;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.require_messages(values_ jsonb[])\nRETURNS jsonb[] LANGUAGE plpgsql IMMUTABLE SET search_path = ''\nAS $$\nDECLARE\n item jsonb;\n total_bytes bigint := 0;\n total_nodes integer := 0;\nBEGIN\n IF values_ IS NULL OR cardinality(values_) < 1 OR cardinality(values_) > 10000\n OR array_ndims(values_) <> 1 OR array_lower(values_, 1) <> 1 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_BATCH_INVALID' USING ERRCODE = '22023';\n END IF;\n FOREACH item IN ARRAY values_ LOOP\n total_nodes := total_nodes + pgmq_public.message_node_count(item);\n IF total_nodes > 100000 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_STRUCTURE_TOO_LARGE' USING ERRCODE = '22023';\n END IF;\n total_bytes := total_bytes + octet_length(convert_to(item::text, 'UTF8'));\n IF total_bytes > 8388608 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_PAYLOAD_TOO_LARGE' USING ERRCODE = '22023';\n END IF;\n END LOOP;\n RETURN values_;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.send(queue_name text, message jsonb, sleep_seconds integer DEFAULT 0)\nRETURNS SETOF text\nLANGUAGE sql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$ 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); $$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.send_batch(queue_name text, messages jsonb[], sleep_seconds integer DEFAULT 0)\nRETURNS SETOF text\nLANGUAGE sql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$ 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); $$;\n\n-- Additive SupaCloud extension; official send/read/archive/delete contracts stay unchanged.\nCREATE OR REPLACE FUNCTION pgmq_public.send_idempotent(\n queue_name text, message jsonb, p_job_key text, sleep_seconds integer DEFAULT 0\n)\nRETURNS TABLE (msg_id text, created boolean)\nLANGUAGE plpgsql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$\nDECLARE\n normalized_queue text := pgmq_public.require_public_queue(queue_name);\n captured_message jsonb := pgmq_public.require_message(message);\n captured_delay integer := pgmq_public.require_seconds(sleep_seconds);\n inserted boolean;\n sent_id bigint;\n existing_id bigint;\n existing_message jsonb;\n existing_delay integer;\nBEGIN\n IF p_job_key IS NULL OR p_job_key !~ '^[A-Za-z0-9_.:@/-]{1,200}$' THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_JOB_KEY_INVALID' USING ERRCODE = '22023';\n END IF;\n INSERT INTO supacloud_queue.job_keys(queue_name, job_key, msg_id, message, sleep_seconds)\n VALUES (normalized_queue, p_job_key, 0, captured_message, captured_delay)\n ON CONFLICT ON CONSTRAINT job_keys_pkey DO NOTHING;\n inserted := FOUND;\n IF NOT inserted THEN\n SELECT keys.msg_id, keys.message, keys.sleep_seconds\n INTO existing_id, existing_message, existing_delay\n FROM supacloud_queue.job_keys AS keys\n WHERE keys.queue_name = normalized_queue AND keys.job_key = p_job_key;\n IF existing_id IS NULL OR existing_id < 1 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_JOB_KEY_UNAVAILABLE' USING ERRCODE = '55000';\n END IF;\n IF existing_message IS DISTINCT FROM captured_message OR existing_delay IS DISTINCT FROM captured_delay THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_JOB_KEY_CONFLICT' USING ERRCODE = '22023';\n END IF;\n -- This is a durable enqueue receipt, not evidence that the message is still pending.\n RETURN QUERY SELECT existing_id::text, false;\n RETURN;\n END IF;\n SELECT receipt.msg_id INTO STRICT sent_id\n FROM pgmq.send(normalized_queue, captured_message, captured_delay) AS receipt(msg_id);\n PERFORM pgmq_public.require_message_id(sent_id);\n UPDATE supacloud_queue.job_keys AS keys SET msg_id = sent_id\n WHERE keys.queue_name = normalized_queue AND keys.job_key = p_job_key;\n RETURN QUERY SELECT sent_id::text, true;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.read(queue_name text, sleep_seconds integer, n integer)\nRETURNS TABLE (\n msg_id text, read_ct integer, enqueued_at timestamptz,\n last_read_at timestamptz, vt timestamptz, message jsonb, headers jsonb\n)\nLANGUAGE sql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$\n SELECT receipt.msg_id::text, receipt.read_ct, receipt.enqueued_at,\n (to_jsonb(receipt)->>'last_read_at')::timestamptz, receipt.vt,\n receipt.message, to_jsonb(receipt)->'headers'\n FROM pgmq.read(pgmq_public.require_public_queue(queue_name),\n pgmq_public.require_seconds(sleep_seconds), pgmq_public.require_read_count(n)) AS receipt;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.pop(queue_name text)\nRETURNS TABLE (\n msg_id text, read_ct integer, enqueued_at timestamptz,\n last_read_at timestamptz, vt timestamptz, message jsonb, headers jsonb\n)\nLANGUAGE sql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$\n SELECT receipt.msg_id::text, receipt.read_ct, receipt.enqueued_at,\n (to_jsonb(receipt)->>'last_read_at')::timestamptz, receipt.vt,\n receipt.message, to_jsonb(receipt)->'headers'\n FROM pgmq.pop(pgmq_public.require_public_queue(queue_name)) AS receipt;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.archive(queue_name text, message_id bigint)\nRETURNS boolean\nLANGUAGE sql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$ SELECT pgmq.archive(pgmq_public.require_public_queue(queue_name), pgmq_public.require_message_id(message_id)); $$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.\"delete\"(queue_name text, message_id bigint)\nRETURNS boolean\nLANGUAGE sql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$ SELECT pgmq.delete(pgmq_public.require_public_queue(queue_name), pgmq_public.require_message_id(message_id)); $$;\n\nREVOKE ALL ON ALL FUNCTIONS IN SCHEMA pgmq_public FROM PUBLIC;\nGRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA pgmq_public TO anon, authenticated, service_role;\n\nNOTIFY pgrst, 'reload schema';\n-- supacloud:sql-module:pgmq-public:end\n";
|
|
21
|
+
export declare const PGMQ_SQL = "\ncreate schema if not exists pgmq;\nrevoke all on schema pgmq from public, anon, authenticated;\ngrant usage on schema pgmq to service_role;\n\ncreate table if not exists pgmq.meta (\n queue_name text primary key,\n physical_name text not null unique,\n is_partitioned boolean not null default false,\n is_unlogged boolean not null default false,\n created_at timestamptz not null default now()\n);\n\n-- Preserve queues created by older Lite versions. Their physical names were\n-- the queue names, so only the already-truncated identifier can be recovered.\ninsert into pgmq.meta (queue_name, physical_name)\nselect substring(tablename from 3), substring(tablename from 3)\nfrom pg_tables\nwhere schemaname = 'pgmq' and left(tablename, 2) = 'q_'\non conflict do nothing;\n\ndo $$ begin\n if not exists (select 1 from pg_type t join pg_namespace n on n.oid = t.typnamespace\n where t.typname = 'message_record' and n.nspname = 'pgmq') then\n create type pgmq.message_record as (\n msg_id bigint, read_ct integer, enqueued_at timestamptz, vt timestamptz, message jsonb\n );\n end if;\nend $$;\n\ncreate or replace function pgmq._normalize_queue_name(queue_name text)\nreturns text language plpgsql immutable set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare normalized text := btrim(queue_name);\nbegin\n if normalized is null or normalized !~ '^[a-z0-9][a-z0-9_-]{0,127}$' then\n raise exception 'invalid queue name: must match ^[a-z0-9][a-z0-9_-]{0,127}$'\n using errcode = '22023';\n end if;\n return normalized;\nend $pgmq$;\n\n-- PostgreSQL identifiers are limited to 63 bytes. Keep the conventional table\n-- name for short queues and add a deterministic hash for valid 62-128 byte\n-- public names so truncation can never merge two queues.\ncreate or replace function pgmq._physical_queue_name(queue_name text)\nreturns text language sql immutable set search_path = pgmq, pg_catalog, public as $pgmq$\n select case when length(normalized) <= 61 then normalized\n else left(normalized, 28) || '_' || md5(normalized) end\n from (select pgmq._normalize_queue_name(queue_name) as normalized) names;\n$pgmq$;\n\ncreate or replace function pgmq._resolve_queue(queue_name text)\nreturns text language plpgsql stable security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare normalized text := pgmq._normalize_queue_name(queue_name); physical text;\nbegin\n select physical_name into physical from pgmq.meta where pgmq.meta.queue_name = normalized;\n if physical is null then\n raise exception 'queue \"%\" does not exist', normalized using errcode = '42P01';\n end if;\n return physical;\nend $pgmq$;\n\ncreate or replace function pgmq.create(queue_name text) returns void language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare normalized text := pgmq._normalize_queue_name(queue_name); physical text := pgmq._physical_queue_name(queue_name);\nbegin\n insert into pgmq.meta (queue_name, physical_name) values (normalized, physical)\n on conflict do nothing;\n select physical_name into physical from pgmq.meta where pgmq.meta.queue_name = normalized;\n execute format('create table if not exists pgmq.%I (\n msg_id bigint generated always as identity primary key,\n read_ct integer not null default 0,\n enqueued_at timestamptz not null default now(),\n vt timestamptz not null default now(),\n message jsonb)', 'q_' || physical);\n execute format('create table if not exists pgmq.%I (\n msg_id bigint primary key, read_ct integer not null default 0,\n enqueued_at timestamptz not null, archived_at timestamptz not null default now(),\n vt timestamptz, message jsonb)', 'a_' || physical);\nend $pgmq$;\n\ncreate or replace function pgmq.send(queue_name text, msg jsonb, delay integer default 0)\nreturns bigint language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare id bigint; physical text := pgmq._resolve_queue(queue_name);\nbegin\n execute format('insert into pgmq.%I (vt, message) values (now() + make_interval(secs => $1), $2) returning msg_id', 'q_' || physical)\n into id using delay, msg;\n return id;\nend $pgmq$;\n\ncreate or replace function pgmq.send_batch(queue_name text, msgs jsonb[], delay integer default 0)\nreturns setof bigint language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare queue_message jsonb;\nbegin\n perform pgmq._resolve_queue(queue_name);\n foreach queue_message in array msgs loop\n return next pgmq.send(queue_name, queue_message, delay);\n end loop;\nend $pgmq$;\n\ncreate or replace function pgmq.read(queue_name text, vt integer, qty integer)\nreturns setof pgmq.message_record language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare physical text := pgmq._resolve_queue(queue_name);\nbegin\n return query execute format($fmt$\n with cte as (\n select msg_id from pgmq.%I where vt <= now() order by msg_id limit $1 for update skip locked\n )\n update pgmq.%I m set vt = now() + make_interval(secs => $2), read_ct = read_ct + 1\n from cte where m.msg_id = cte.msg_id\n returning m.msg_id, m.read_ct, m.enqueued_at, m.vt, m.message\n $fmt$, 'q_' || physical, 'q_' || physical) using qty, vt;\nend $pgmq$;\n\ncreate or replace function pgmq.pop(queue_name text)\nreturns setof pgmq.message_record language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare physical text := pgmq._resolve_queue(queue_name);\nbegin\n return query execute format($fmt$\n with cte as (select msg_id from pgmq.%I where vt <= now() order by msg_id limit 1 for update skip locked)\n delete from pgmq.%I m using cte where m.msg_id = cte.msg_id\n returning m.msg_id, m.read_ct, m.enqueued_at, m.vt, m.message\n $fmt$, 'q_' || physical, 'q_' || physical);\nend $pgmq$;\n\ncreate or replace function pgmq.set_vt(queue_name text, msg_id bigint, vt_offset integer)\nreturns setof pgmq.message_record language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare physical text := pgmq._resolve_queue(queue_name);\nbegin\n return query execute format($fmt$\n update pgmq.%I m set vt = now() + make_interval(secs => $1)\n where m.msg_id = $2\n returning m.msg_id, m.read_ct, m.enqueued_at, m.vt, m.message\n $fmt$, 'q_' || physical) using vt_offset, msg_id;\nend $pgmq$;\n\ncreate or replace function pgmq.delete(queue_name text, msg_id bigint)\nreturns boolean language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare n integer; physical text := pgmq._resolve_queue(queue_name);\nbegin\n execute format('delete from pgmq.%I where msg_id = $1', 'q_' || physical) using msg_id;\n get diagnostics n = row_count;\n return n > 0;\nend $pgmq$;\n\ncreate or replace function pgmq.archive(queue_name text, msg_id bigint)\nreturns boolean language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare n integer; physical text := pgmq._resolve_queue(queue_name);\nbegin\n execute format($fmt$\n with del as (delete from pgmq.%I where msg_id = $1 returning *)\n insert into pgmq.%I (msg_id, read_ct, enqueued_at, vt, message)\n select msg_id, read_ct, enqueued_at, vt, message from del\n $fmt$, 'q_' || physical, 'a_' || physical) using msg_id;\n get diagnostics n = row_count;\n return n > 0;\nend $pgmq$;\n\ncreate or replace function pgmq.drop_queue(queue_name text) returns boolean language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare normalized text := pgmq._normalize_queue_name(queue_name); physical text;\nbegin\n select physical_name into physical from pgmq.meta where pgmq.meta.queue_name = normalized;\n if physical is null then return false; end if;\n execute format('drop table if exists pgmq.%I', 'q_' || physical);\n execute format('drop table if exists pgmq.%I', 'a_' || physical);\n delete from pgmq.meta where pgmq.meta.queue_name = normalized;\n return true;\nend $pgmq$;\n\ncreate or replace function pgmq.purge_queue(queue_name text) returns bigint language plpgsql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\ndeclare n bigint; physical text := pgmq._resolve_queue(queue_name);\nbegin\n execute format('delete from pgmq.%I', 'q_' || physical);\n get diagnostics n = row_count;\n return n;\nend $pgmq$;\n\ncreate or replace function pgmq.list_queues()\nreturns table(queue_name text, is_partitioned boolean, is_unlogged boolean, created_at timestamptz)\nlanguage sql security definer set search_path = pgmq, pg_catalog, public as $pgmq$\n select queue_name, is_partitioned, is_unlogged, created_at from pgmq.meta;\n$pgmq$;\n\nrevoke all on all functions in schema pgmq from public, anon, authenticated;\ngrant execute on all functions in schema pgmq to service_role;\n\n\n\n-- supacloud:sql-module:pgmq-public:start\nDO $pgmq_extension$\nBEGIN\n IF to_regprocedure('pgmq.send(text,jsonb,integer)') IS NULL THEN\n EXECUTE 'CREATE EXTENSION IF NOT EXISTS pgmq';\n END IF;\nEND\n$pgmq_extension$;\n\nCREATE SCHEMA IF NOT EXISTS pgmq_public;\nGRANT USAGE ON SCHEMA pgmq_public TO anon, authenticated, service_role;\nCREATE SCHEMA IF NOT EXISTS supacloud_queue;\nCREATE TABLE IF NOT EXISTS supacloud_queue.job_keys (\n queue_name text NOT NULL,\n job_key text NOT NULL,\n msg_id bigint NOT NULL,\n message jsonb NOT NULL,\n sleep_seconds integer NOT NULL,\n created_at timestamptz NOT NULL DEFAULT clock_timestamp(),\n PRIMARY KEY (queue_name, job_key)\n);\n-- Upgrade early key-only installations without inventing an input identity.\nALTER TABLE supacloud_queue.job_keys ADD COLUMN IF NOT EXISTS message jsonb;\nALTER TABLE supacloud_queue.job_keys ADD COLUMN IF NOT EXISTS sleep_seconds integer;\nREVOKE ALL ON SCHEMA supacloud_queue FROM PUBLIC, anon, authenticated, service_role;\nREVOKE ALL ON TABLE supacloud_queue.job_keys FROM PUBLIC, anon, authenticated, service_role;\n\nCREATE OR REPLACE FUNCTION pgmq_public.require_public_queue(queue_name text)\nRETURNS text\nLANGUAGE plpgsql\nIMMUTABLE\nSET search_path = ''\nAS $$\nDECLARE\n normalized_queue_name text := lower(btrim(queue_name));\nBEGIN\n IF left(normalized_queue_name, char_length('supacloud_internal_')) = 'supacloud_internal_' THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_NAME_RESERVED' USING ERRCODE = '42501';\n END IF;\n IF queue_name IS NULL OR queue_name !~ '^[a-z0-9][a-z0-9_-]{0,127}$' THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_NAME_INVALID' USING ERRCODE = '22023';\n END IF;\n RETURN queue_name;\nEND;\n$$;\n\n-- Return-type changes require replacement. Do not CASCADE through user dependencies.\nDO $pgmq_receipt_types$\nBEGIN\n IF EXISTS (\n SELECT 1 FROM pg_catalog.pg_proc\n WHERE oid = to_regprocedure('pgmq_public.send(text,jsonb,integer)')\n AND prorettype = 'bigint'::regtype\n ) THEN\n DROP FUNCTION pgmq_public.send(text,jsonb,integer);\n END IF;\n IF EXISTS (\n SELECT 1 FROM pg_catalog.pg_proc\n WHERE oid = to_regprocedure('pgmq_public.send_batch(text,jsonb[],integer)')\n AND prorettype = 'bigint'::regtype\n ) THEN\n DROP FUNCTION pgmq_public.send_batch(text,jsonb[],integer);\n END IF;\n IF EXISTS (\n SELECT 1 FROM pg_catalog.pg_proc\n WHERE oid = to_regprocedure('pgmq_public.read(text,integer,integer)')\n AND prorettype = 'pgmq.message_record'::regtype\n ) THEN\n DROP FUNCTION pgmq_public.read(text,integer,integer);\n END IF;\n IF EXISTS (\n SELECT 1 FROM pg_catalog.pg_proc\n WHERE oid = to_regprocedure('pgmq_public.pop(text)')\n AND prorettype = 'pgmq.message_record'::regtype\n ) THEN\n DROP FUNCTION pgmq_public.pop(text);\n END IF;\nEND\n$pgmq_receipt_types$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.require_seconds(value integer)\nRETURNS integer LANGUAGE plpgsql IMMUTABLE SET search_path = ''\nAS $$\nBEGIN\n IF value IS NULL OR value < 0 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_SECONDS_INVALID' USING ERRCODE = '22023';\n END IF;\n RETURN value;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.require_read_count(value integer)\nRETURNS integer LANGUAGE plpgsql IMMUTABLE SET search_path = ''\nAS $$\nBEGIN\n IF value IS NULL OR value < 1 OR value > 10000 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_COUNT_INVALID' USING ERRCODE = '22023';\n END IF;\n RETURN value;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.require_message_id(value bigint)\nRETURNS bigint LANGUAGE plpgsql IMMUTABLE SET search_path = ''\nAS $$\nBEGIN\n IF value IS NULL OR value < 1 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_MESSAGE_ID_INVALID' USING ERRCODE = '22023';\n END IF;\n RETURN value;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.message_node_count(value jsonb)\nRETURNS integer LANGUAGE plpgsql IMMUTABLE SET search_path = ''\nAS $$\nDECLARE\n node_count integer;\n maximum_depth integer;\nBEGIN\n IF value IS NULL THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_MESSAGE_INVALID' USING ERRCODE = '22023';\n END IF;\n IF octet_length(convert_to(value::text, 'UTF8')) > 1048576 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_PAYLOAD_TOO_LARGE' USING ERRCODE = '22023';\n END IF;\n WITH RECURSIVE tree(item, depth) AS (\n SELECT value, 0\n UNION ALL\n SELECT child.item, tree.depth + 1\n FROM tree\n CROSS JOIN LATERAL (\n SELECT element AS item\n FROM jsonb_array_elements(CASE WHEN jsonb_typeof(tree.item) = 'array' THEN tree.item ELSE '[]'::jsonb END) AS element\n UNION ALL\n SELECT entry.value AS item\n FROM jsonb_each(CASE WHEN jsonb_typeof(tree.item) = 'object' THEN tree.item ELSE '{}'::jsonb END) AS entry\n ) AS child\n WHERE tree.depth < 65\n )\n SELECT count(*)::integer, max(depth) INTO node_count, maximum_depth\n FROM (SELECT depth FROM tree LIMIT 10001) AS bounded;\n IF node_count > 10000 OR maximum_depth > 64 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_STRUCTURE_TOO_LARGE' USING ERRCODE = '22023';\n END IF;\n RETURN node_count;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.require_message(value jsonb)\nRETURNS jsonb LANGUAGE plpgsql IMMUTABLE SET search_path = ''\nAS $$\nBEGIN\n PERFORM pgmq_public.message_node_count(value);\n RETURN value;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.require_messages(values_ jsonb[])\nRETURNS jsonb[] LANGUAGE plpgsql IMMUTABLE SET search_path = ''\nAS $$\nDECLARE\n item jsonb;\n total_bytes bigint := 0;\n total_nodes integer := 0;\nBEGIN\n IF values_ IS NULL OR cardinality(values_) < 1 OR cardinality(values_) > 10000\n OR array_ndims(values_) <> 1 OR array_lower(values_, 1) <> 1 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_BATCH_INVALID' USING ERRCODE = '22023';\n END IF;\n FOREACH item IN ARRAY values_ LOOP\n total_nodes := total_nodes + pgmq_public.message_node_count(item);\n IF total_nodes > 100000 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_STRUCTURE_TOO_LARGE' USING ERRCODE = '22023';\n END IF;\n total_bytes := total_bytes + octet_length(convert_to(item::text, 'UTF8'));\n IF total_bytes > 8388608 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_PAYLOAD_TOO_LARGE' USING ERRCODE = '22023';\n END IF;\n END LOOP;\n RETURN values_;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.send(queue_name text, message jsonb, sleep_seconds integer DEFAULT 0)\nRETURNS SETOF text\nLANGUAGE sql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$ 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); $$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.send_batch(queue_name text, messages jsonb[], sleep_seconds integer DEFAULT 0)\nRETURNS SETOF text\nLANGUAGE sql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$ 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); $$;\n\n-- Additive SupaCloud extension; official send/read/archive/delete contracts stay unchanged.\nCREATE OR REPLACE FUNCTION pgmq_public.send_idempotent(\n queue_name text, message jsonb, p_job_key text, sleep_seconds integer DEFAULT 0\n)\nRETURNS TABLE (msg_id text, created boolean)\nLANGUAGE plpgsql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$\nDECLARE\n normalized_queue text := pgmq_public.require_public_queue(queue_name);\n captured_message jsonb := pgmq_public.require_message(message);\n captured_delay integer := pgmq_public.require_seconds(sleep_seconds);\n inserted boolean;\n sent_id bigint;\n existing_id bigint;\n existing_message jsonb;\n existing_delay integer;\nBEGIN\n IF p_job_key IS NULL OR p_job_key !~ '^[A-Za-z0-9_.:@/-]{1,200}$' THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_JOB_KEY_INVALID' USING ERRCODE = '22023';\n END IF;\n INSERT INTO supacloud_queue.job_keys(queue_name, job_key, msg_id, message, sleep_seconds)\n VALUES (normalized_queue, p_job_key, 0, captured_message, captured_delay)\n ON CONFLICT ON CONSTRAINT job_keys_pkey DO NOTHING;\n inserted := FOUND;\n IF NOT inserted THEN\n SELECT keys.msg_id, keys.message, keys.sleep_seconds\n INTO existing_id, existing_message, existing_delay\n FROM supacloud_queue.job_keys AS keys\n WHERE keys.queue_name = normalized_queue AND keys.job_key = p_job_key;\n IF existing_id IS NULL OR existing_id < 1 THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_JOB_KEY_UNAVAILABLE' USING ERRCODE = '55000';\n END IF;\n IF existing_message IS DISTINCT FROM captured_message OR existing_delay IS DISTINCT FROM captured_delay THEN\n RAISE EXCEPTION 'SUPACLOUD_QUEUE_JOB_KEY_CONFLICT' USING ERRCODE = '22023';\n END IF;\n -- This is a durable enqueue receipt, not evidence that the message is still pending.\n RETURN QUERY SELECT existing_id::text, false;\n RETURN;\n END IF;\n SELECT receipt.msg_id INTO STRICT sent_id\n FROM pgmq.send(normalized_queue, captured_message, captured_delay) AS receipt(msg_id);\n PERFORM pgmq_public.require_message_id(sent_id);\n UPDATE supacloud_queue.job_keys AS keys SET msg_id = sent_id\n WHERE keys.queue_name = normalized_queue AND keys.job_key = p_job_key;\n RETURN QUERY SELECT sent_id::text, true;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.read(queue_name text, sleep_seconds integer, n integer)\nRETURNS TABLE (\n msg_id text, read_ct integer, enqueued_at timestamptz,\n last_read_at timestamptz, vt timestamptz, message jsonb, headers jsonb\n)\nLANGUAGE sql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$\n SELECT receipt.msg_id::text, receipt.read_ct, receipt.enqueued_at,\n (to_jsonb(receipt)->>'last_read_at')::timestamptz, receipt.vt,\n receipt.message, to_jsonb(receipt)->'headers'\n FROM pgmq.read(pgmq_public.require_public_queue(queue_name),\n pgmq_public.require_seconds(sleep_seconds), pgmq_public.require_read_count(n)) AS receipt;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.pop(queue_name text)\nRETURNS TABLE (\n msg_id text, read_ct integer, enqueued_at timestamptz,\n last_read_at timestamptz, vt timestamptz, message jsonb, headers jsonb\n)\nLANGUAGE sql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$\n SELECT receipt.msg_id::text, receipt.read_ct, receipt.enqueued_at,\n (to_jsonb(receipt)->>'last_read_at')::timestamptz, receipt.vt,\n receipt.message, to_jsonb(receipt)->'headers'\n FROM pgmq.pop(pgmq_public.require_public_queue(queue_name)) AS receipt;\n$$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.archive(queue_name text, message_id bigint)\nRETURNS boolean\nLANGUAGE sql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$ SELECT pgmq.archive(pgmq_public.require_public_queue(queue_name), pgmq_public.require_message_id(message_id)); $$;\n\nCREATE OR REPLACE FUNCTION pgmq_public.\"delete\"(queue_name text, message_id bigint)\nRETURNS boolean\nLANGUAGE sql\nVOLATILE\nSECURITY DEFINER\nSET search_path = ''\nAS $$ SELECT pgmq.delete(pgmq_public.require_public_queue(queue_name), pgmq_public.require_message_id(message_id)); $$;\n\nREVOKE ALL ON ALL FUNCTIONS IN SCHEMA pgmq_public FROM PUBLIC;\nGRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA pgmq_public TO anon, authenticated, service_role;\n\nNOTIFY pgrst, 'reload schema';\n-- supacloud:sql-module:pgmq-public:end\n";
|
|
17
22
|
export declare const WORKFLOWS_SQL = "\n-- supacloud:sql-module:workflows-public:start\nDO $pgmq_extension$\nBEGIN\n IF to_regprocedure('pgmq.send(text,jsonb,integer)') IS NULL THEN\n EXECUTE 'CREATE EXTENSION IF NOT EXISTS pgmq';\n END IF;\nEND\n$pgmq_extension$;\nCREATE SCHEMA IF NOT EXISTS supacloud_workflows;\nREVOKE ALL ON SCHEMA supacloud_workflows FROM PUBLIC, anon, authenticated;\nGRANT USAGE ON SCHEMA supacloud_workflows TO service_role;\n\nSELECT pgmq.create('supacloud_internal_workflows');\n\nCREATE TABLE IF NOT EXISTS supacloud_workflows.runs (\n id uuid PRIMARY KEY,\n workflow_name text NOT NULL\n CHECK (workflow_name ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$'),\n workflow_version text NOT NULL\n CHECK (char_length(workflow_version) BETWEEN 1 AND 80),\n status text NOT NULL DEFAULT 'queued'\n CHECK (status IN ('queued', 'running', 'completed', 'failed', 'cancelled')),\n input jsonb NOT NULL DEFAULT '{}'::jsonb\n CHECK (jsonb_typeof(input) = 'object'),\n output jsonb NOT NULL DEFAULT '{}'::jsonb\n CHECK (jsonb_typeof(output) = 'object'),\n error_message text NOT NULL DEFAULT ''\n CHECK (char_length(error_message) <= 4000),\n row_version bigint NOT NULL DEFAULT 1 CHECK (row_version > 0),\n created_at timestamptz NOT NULL DEFAULT now(),\n started_at timestamptz,\n completed_at timestamptz,\n updated_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE TABLE IF NOT EXISTS supacloud_workflows.steps (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n run_id uuid NOT NULL REFERENCES supacloud_workflows.runs(id) ON DELETE CASCADE,\n step_key text NOT NULL\n CHECK (step_key ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$'),\n status text NOT NULL DEFAULT 'queued'\n CHECK (status IN ('queued', 'running', 'completed', 'failed', 'dead_lettered', 'cancelled')),\n input jsonb NOT NULL DEFAULT '{}'::jsonb\n CHECK (jsonb_typeof(input) = 'object'),\n output jsonb NOT NULL DEFAULT '{}'::jsonb\n CHECK (jsonb_typeof(output) = 'object'),\n error_message text NOT NULL DEFAULT ''\n CHECK (char_length(error_message) <= 4000),\n attempts integer NOT NULL DEFAULT 0 CHECK (attempts >= 0),\n max_attempts integer NOT NULL DEFAULT 3 CHECK (max_attempts BETWEEN 1 AND 100),\n retry_delay_seconds integer NOT NULL DEFAULT 0\n CHECK (retry_delay_seconds BETWEEN 0 AND 86400),\n queue_message_id bigint NOT NULL UNIQUE,\n claimed_by text,\n claimed_at timestamptz,\n completed_at timestamptz,\n next_step_key text,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n UNIQUE (run_id, step_key)\n);\n\nALTER TABLE supacloud_workflows.steps\n ADD COLUMN IF NOT EXISTS retry_delay_seconds integer NOT NULL DEFAULT 0\n CHECK (retry_delay_seconds BETWEEN 0 AND 86400);\n\nCREATE UNIQUE INDEX IF NOT EXISTS supacloud_workflows_one_active_step_idx\n ON supacloud_workflows.steps (run_id)\n WHERE status IN ('queued', 'running');\n\nCREATE INDEX IF NOT EXISTS supacloud_workflows_runs_status_idx\n ON supacloud_workflows.runs (status, updated_at DESC, id);\n\nCREATE INDEX IF NOT EXISTS supacloud_workflows_steps_run_idx\n ON supacloud_workflows.steps (run_id, created_at, id);\n\nCREATE TABLE IF NOT EXISTS supacloud_workflows.events (\n id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n run_id uuid NOT NULL REFERENCES supacloud_workflows.runs(id) ON DELETE CASCADE,\n step_id uuid REFERENCES supacloud_workflows.steps(id) ON DELETE CASCADE,\n event_type text NOT NULL\n CHECK (event_type IN (\n 'run_started', 'step_claimed', 'step_retried', 'step_completed',\n 'step_failed', 'step_dead_lettered', 'run_completed', 'run_cancelled'\n )),\n attempt integer CHECK (attempt IS NULL OR attempt > 0),\n details jsonb NOT NULL DEFAULT '{}'::jsonb\n CHECK (jsonb_typeof(details) = 'object'),\n created_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE INDEX IF NOT EXISTS supacloud_workflows_events_run_idx\n ON supacloud_workflows.events (run_id, id);\n\nCREATE UNIQUE INDEX IF NOT EXISTS supacloud_workflows_retry_receipt_idx\n ON supacloud_workflows.events (step_id, attempt)\n WHERE event_type IN ('step_retried', 'step_dead_lettered')\n AND details ->> 'operation' = 'retry';\n\nCREATE OR REPLACE FUNCTION supacloud_workflows.snapshot(\n p_run_id uuid,\n p_idempotent boolean DEFAULT false\n) RETURNS jsonb\nLANGUAGE sql STABLE SECURITY DEFINER SET search_path = '' AS $$\n SELECT jsonb_build_object(\n 'runId', run.id,\n 'workflowName', run.workflow_name,\n 'workflowVersion', run.workflow_version,\n 'status', run.status,\n 'input', run.input,\n 'output', run.output,\n 'errorMessage', run.error_message,\n 'rowVersion', run.row_version::text,\n 'createdAt', run.created_at,\n 'startedAt', run.started_at,\n 'completedAt', run.completed_at,\n 'updatedAt', run.updated_at,\n 'idempotent', p_idempotent,\n 'steps', coalesce((\n SELECT jsonb_agg(jsonb_build_object(\n 'stepId', step.id,\n 'stepKey', step.step_key,\n 'status', step.status,\n 'input', step.input,\n 'output', step.output,\n 'errorMessage', step.error_message,\n 'attempts', step.attempts,\n 'maxAttempts', step.max_attempts,\n 'retryDelaySeconds', step.retry_delay_seconds,\n 'queueMessageId', step.queue_message_id::text,\n 'claimedBy', step.claimed_by,\n 'claimedAt', step.claimed_at,\n 'completedAt', step.completed_at,\n 'nextStepKey', step.next_step_key,\n 'createdAt', step.created_at,\n 'updatedAt', step.updated_at\n ) ORDER BY step.created_at, step.id)\n FROM supacloud_workflows.steps step\n WHERE step.run_id = run.id\n ), '[]'::jsonb)\n )\n FROM supacloud_workflows.runs run\n WHERE run.id = p_run_id\n$$;\n\nCREATE OR REPLACE FUNCTION supacloud_workflows.enqueue_step(\n p_run_id uuid,\n p_step_key text,\n p_input jsonb,\n p_max_attempts integer\n) RETURNS supacloud_workflows.steps\nLANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n normalized_step_key text := nullif(btrim(p_step_key), '');\n step_id uuid := gen_random_uuid();\n message_id bigint;\n created_step supacloud_workflows.steps%ROWTYPE;\nBEGIN\n IF normalized_step_key IS NULL\n OR normalized_step_key !~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$'\n OR jsonb_typeof(p_input) IS DISTINCT FROM 'object'\n OR p_max_attempts NOT BETWEEN 1 AND 100 THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_STEP_INVALID' USING ERRCODE = '22023';\n END IF;\n\n IF NOT EXISTS (\n SELECT 1 FROM supacloud_workflows.runs run\n WHERE run.id = p_run_id AND run.status IN ('queued', 'running')\n ) THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_RUN_NOT_ACTIVE' USING ERRCODE = '55000';\n END IF;\n\n SELECT queued_id INTO message_id\n FROM pgmq.send(\n 'supacloud_internal_workflows',\n jsonb_build_object('run_id', p_run_id, 'step_id', step_id),\n 0\n ) AS queued_id;\n\n INSERT INTO supacloud_workflows.steps (\n id, run_id, step_key, input, max_attempts, queue_message_id\n ) VALUES (\n step_id, p_run_id, normalized_step_key, p_input, p_max_attempts, message_id\n ) RETURNING * INTO created_step;\n\n RETURN created_step;\nEND;\n$$;\n\n-- Clean-code exception: private transitions keep typed PostgreSQL arguments and\n-- the complete lock/queue/ledger/event mutation in one transaction. The public\n-- contract already uses one JSON request; revisit if a private routine gains a\n-- second caller or any transition can be decomposed without weakening atomicity.\nCREATE OR REPLACE FUNCTION supacloud_workflows.start_run(\n p_run_id uuid,\n p_workflow_name text,\n p_workflow_version text,\n p_first_step_key text,\n p_input jsonb,\n p_max_attempts integer\n) RETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n normalized_name text := nullif(btrim(p_workflow_name), '');\n normalized_version text := nullif(btrim(p_workflow_version), '');\n normalized_first_step_key text := nullif(btrim(p_first_step_key), '');\n existing_run supacloud_workflows.runs%ROWTYPE;\n existing_step supacloud_workflows.steps%ROWTYPE;\n first_step supacloud_workflows.steps%ROWTYPE;\nBEGIN\n IF p_run_id IS NULL\n OR normalized_name IS NULL\n OR normalized_name !~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$'\n OR normalized_version IS NULL\n OR char_length(normalized_version) > 80\n OR normalized_first_step_key IS NULL\n OR normalized_first_step_key !~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$'\n OR jsonb_typeof(p_input) IS DISTINCT FROM 'object'\n OR p_max_attempts NOT BETWEEN 1 AND 100 THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_START_INVALID' USING ERRCODE = '22023';\n END IF;\n\n PERFORM pg_advisory_xact_lock(hashtextextended(p_run_id::text, 0));\n SELECT * INTO existing_run FROM supacloud_workflows.runs WHERE id = p_run_id;\n IF FOUND THEN\n SELECT * INTO existing_step\n FROM supacloud_workflows.steps\n WHERE run_id = p_run_id\n ORDER BY created_at, id\n LIMIT 1;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_IDEMPOTENCY_CONFLICT' USING ERRCODE = '23505';\n END IF;\n IF existing_run.workflow_name <> normalized_name\n OR existing_run.workflow_version <> normalized_version\n OR existing_run.input <> p_input\n OR existing_step.step_key <> normalized_first_step_key\n OR existing_step.input <> p_input\n OR existing_step.max_attempts <> p_max_attempts THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_IDEMPOTENCY_CONFLICT' USING ERRCODE = '23505';\n END IF;\n RETURN supacloud_workflows.snapshot(p_run_id, true);\n END IF;\n\n INSERT INTO supacloud_workflows.runs (\n id, workflow_name, workflow_version, input\n ) VALUES (\n p_run_id, normalized_name, normalized_version, p_input\n );\n first_step := supacloud_workflows.enqueue_step(\n p_run_id, normalized_first_step_key, p_input, p_max_attempts\n );\n INSERT INTO supacloud_workflows.events (run_id, step_id, event_type)\n VALUES (p_run_id, first_step.id, 'run_started');\n RETURN supacloud_workflows.snapshot(p_run_id, false);\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION supacloud_workflows.claim_step(\n p_worker_id text,\n p_visibility_timeout_seconds integer\n) RETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n normalized_worker_id text := nullif(btrim(p_worker_id), '');\n queued_message pgmq.message_record;\n message_run_id text;\n message_step_id text;\n candidate_run_id uuid;\n claimed_step supacloud_workflows.steps%ROWTYPE;\n claimed_run supacloud_workflows.runs%ROWTYPE;\nBEGIN\n IF normalized_worker_id IS NULL\n OR char_length(normalized_worker_id) > 200\n OR p_visibility_timeout_seconds NOT BETWEEN 15 AND 3600 THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_CLAIM_INVALID' USING ERRCODE = '22023';\n END IF;\n\n SELECT * INTO queued_message\n FROM pgmq.read('supacloud_internal_workflows', p_visibility_timeout_seconds, 1)\n LIMIT 1;\n IF NOT FOUND THEN RETURN NULL; END IF;\n\n message_run_id := queued_message.message ->> 'run_id';\n message_step_id := queued_message.message ->> 'step_id';\n IF jsonb_typeof(queued_message.message) IS DISTINCT FROM 'object'\n OR message_run_id IS NULL\n OR message_step_id IS NULL\n OR message_run_id !~* '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'\n OR message_step_id !~* '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' THEN\n PERFORM pgmq.archive('supacloud_internal_workflows', queued_message.msg_id);\n RETURN jsonb_build_object(\n 'status', 'discarded',\n 'reason', 'invalid_message',\n 'messageId', queued_message.msg_id::text\n );\n END IF;\n\n SELECT step.run_id INTO candidate_run_id\n FROM supacloud_workflows.steps step\n WHERE step.id::text = lower(message_step_id)\n AND step.run_id::text = lower(message_run_id)\n AND step.queue_message_id = queued_message.msg_id;\n IF NOT FOUND THEN\n PERFORM pgmq.archive('supacloud_internal_workflows', queued_message.msg_id);\n RETURN jsonb_build_object(\n 'status', 'discarded',\n 'reason', 'orphaned_message',\n 'messageId', queued_message.msg_id::text\n );\n END IF;\n\n IF NOT pg_try_advisory_xact_lock(hashtextextended(candidate_run_id::text, 0)) THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_CLAIM_RETRY' USING ERRCODE = '40001';\n END IF;\n SELECT * INTO claimed_step\n FROM supacloud_workflows.steps\n WHERE id::text = lower(message_step_id)\n AND run_id = candidate_run_id\n AND queue_message_id = queued_message.msg_id\n FOR UPDATE;\n IF NOT FOUND THEN\n PERFORM pgmq.archive('supacloud_internal_workflows', queued_message.msg_id);\n RETURN jsonb_build_object(\n 'status', 'discarded',\n 'reason', 'orphaned_message',\n 'messageId', queued_message.msg_id::text\n );\n END IF;\n\n SELECT * INTO claimed_run\n FROM supacloud_workflows.runs\n WHERE id = claimed_step.run_id\n FOR UPDATE;\n IF claimed_run.status NOT IN ('queued', 'running')\n OR claimed_step.status NOT IN ('queued', 'running') THEN\n PERFORM pgmq.archive('supacloud_internal_workflows', queued_message.msg_id);\n RETURN jsonb_build_object(\n 'status', 'discarded',\n 'reason', 'step_not_claimable',\n 'runId', claimed_step.run_id,\n 'stepId', claimed_step.id,\n 'messageId', queued_message.msg_id::text\n );\n END IF;\n\n IF queued_message.read_ct > claimed_step.max_attempts THEN\n UPDATE supacloud_workflows.steps\n SET status = 'dead_lettered', attempts = queued_message.read_ct,\n error_message = 'maximum attempts exceeded', completed_at = now(), updated_at = now()\n WHERE id = claimed_step.id;\n UPDATE supacloud_workflows.runs\n SET status = 'failed', error_message = 'maximum attempts exceeded',\n completed_at = now(), updated_at = now(), row_version = row_version + 1\n WHERE id = claimed_step.run_id;\n PERFORM pgmq.archive('supacloud_internal_workflows', queued_message.msg_id);\n INSERT INTO supacloud_workflows.events (\n run_id, step_id, event_type, attempt, details\n ) VALUES (\n claimed_step.run_id, claimed_step.id, 'step_dead_lettered', queued_message.read_ct,\n jsonb_build_object('errorMessage', 'maximum attempts exceeded')\n );\n RETURN jsonb_build_object(\n 'status', 'dead_lettered',\n 'runId', claimed_step.run_id,\n 'stepId', claimed_step.id,\n 'stepKey', claimed_step.step_key,\n 'messageId', queued_message.msg_id::text,\n 'attempt', queued_message.read_ct,\n 'maxAttempts', claimed_step.max_attempts\n );\n END IF;\n\n UPDATE supacloud_workflows.steps\n SET status = 'running', attempts = queued_message.read_ct,\n retry_delay_seconds = 0, claimed_by = normalized_worker_id,\n claimed_at = now(), updated_at = now()\n WHERE id = claimed_step.id;\n UPDATE supacloud_workflows.runs\n SET status = 'running', started_at = coalesce(started_at, now()),\n updated_at = now(), row_version = row_version + 1\n WHERE id = claimed_step.run_id;\n INSERT INTO supacloud_workflows.events (run_id, step_id, event_type, attempt, details)\n VALUES (\n claimed_step.run_id, claimed_step.id, 'step_claimed', queued_message.read_ct,\n jsonb_build_object('workerId', normalized_worker_id)\n );\n\n RETURN jsonb_build_object(\n 'status', 'claimed',\n 'runId', claimed_step.run_id,\n 'workflowName', claimed_run.workflow_name,\n 'workflowVersion', claimed_run.workflow_version,\n 'stepId', claimed_step.id,\n 'stepKey', claimed_step.step_key,\n 'input', claimed_step.input,\n 'messageId', queued_message.msg_id::text,\n 'attempt', queued_message.read_ct,\n 'maxAttempts', claimed_step.max_attempts,\n 'workerId', normalized_worker_id\n );\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION supacloud_workflows.lock_step(\n p_step_id uuid\n) RETURNS supacloud_workflows.steps\nLANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n candidate_run_id uuid;\n active_step supacloud_workflows.steps%ROWTYPE;\nBEGIN\n SELECT step.run_id INTO candidate_run_id\n FROM supacloud_workflows.steps step\n WHERE step.id = p_step_id;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_STEP_NOT_FOUND' USING ERRCODE = 'P0002';\n END IF;\n PERFORM pg_advisory_xact_lock(hashtextextended(candidate_run_id::text, 0));\n SELECT * INTO active_step\n FROM supacloud_workflows.steps\n WHERE id = p_step_id\n FOR UPDATE;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_STEP_NOT_FOUND' USING ERRCODE = 'P0002';\n END IF;\n RETURN active_step;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION supacloud_workflows.lock_step_attempt(\n p_step_id uuid,\n p_message_id bigint,\n p_attempt integer,\n p_worker_id text\n) RETURNS supacloud_workflows.steps\nLANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n active_step supacloud_workflows.steps%ROWTYPE;\n normalized_worker_id text := nullif(btrim(p_worker_id), '');\nBEGIN\n active_step := supacloud_workflows.lock_step(p_step_id);\n IF normalized_worker_id IS NULL\n OR p_message_id IS NULL\n OR p_attempt IS NULL\n OR active_step.queue_message_id IS DISTINCT FROM p_message_id\n OR active_step.attempts IS DISTINCT FROM p_attempt\n OR active_step.claimed_by IS DISTINCT FROM normalized_worker_id THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_STALE_ATTEMPT' USING ERRCODE = '40001';\n END IF;\n RETURN active_step;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION supacloud_workflows.advance_step(\n p_step_id uuid,\n p_message_id bigint,\n p_attempt integer,\n p_worker_id text,\n p_output jsonb,\n p_next_step_key text,\n p_next_input jsonb,\n p_next_max_attempts integer\n) RETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n current_step supacloud_workflows.steps%ROWTYPE;\n next_step supacloud_workflows.steps%ROWTYPE;\n normalized_next_step_key text := nullif(btrim(p_next_step_key), '');\n archived boolean;\nBEGIN\n IF jsonb_typeof(p_output) IS DISTINCT FROM 'object'\n OR jsonb_typeof(p_next_input) IS DISTINCT FROM 'object'\n OR normalized_next_step_key IS NULL\n OR normalized_next_step_key !~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$'\n OR p_next_max_attempts NOT BETWEEN 1 AND 100 THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_ADVANCE_INVALID' USING ERRCODE = '22023';\n END IF;\n current_step := supacloud_workflows.lock_step_attempt(\n p_step_id, p_message_id, p_attempt, p_worker_id\n );\n\n IF current_step.status = 'completed' THEN\n SELECT * INTO next_step\n FROM supacloud_workflows.steps\n WHERE run_id = current_step.run_id AND step_key = normalized_next_step_key;\n IF NOT FOUND\n OR current_step.output IS DISTINCT FROM p_output\n OR current_step.next_step_key IS DISTINCT FROM normalized_next_step_key\n OR next_step.input IS DISTINCT FROM p_next_input\n OR next_step.max_attempts IS DISTINCT FROM p_next_max_attempts THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_IDEMPOTENCY_CONFLICT' USING ERRCODE = '23505';\n END IF;\n RETURN supacloud_workflows.snapshot(current_step.run_id, true);\n END IF;\n IF current_step.status <> 'running' THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_STALE_ATTEMPT' USING ERRCODE = '40001';\n END IF;\n\n SELECT pgmq.archive('supacloud_internal_workflows', p_message_id) INTO archived;\n IF archived IS DISTINCT FROM true THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_QUEUE_MESSAGE_MISSING' USING ERRCODE = '55000';\n END IF;\n UPDATE supacloud_workflows.steps\n SET status = 'completed', output = p_output, error_message = '',\n completed_at = now(), next_step_key = normalized_next_step_key, updated_at = now()\n WHERE id = current_step.id;\n INSERT INTO supacloud_workflows.events (\n run_id, step_id, event_type, attempt, details\n ) VALUES (\n current_step.run_id, current_step.id, 'step_completed', p_attempt,\n jsonb_build_object('nextStepKey', normalized_next_step_key)\n );\n next_step := supacloud_workflows.enqueue_step(\n current_step.run_id, normalized_next_step_key, p_next_input, p_next_max_attempts\n );\n RETURN supacloud_workflows.snapshot(current_step.run_id, false);\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION supacloud_workflows.complete_run(\n p_step_id uuid,\n p_message_id bigint,\n p_attempt integer,\n p_worker_id text,\n p_step_output jsonb,\n p_run_output jsonb\n) RETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n current_step supacloud_workflows.steps%ROWTYPE;\n current_run supacloud_workflows.runs%ROWTYPE;\n archived boolean;\nBEGIN\n IF jsonb_typeof(p_step_output) IS DISTINCT FROM 'object'\n OR jsonb_typeof(p_run_output) IS DISTINCT FROM 'object' THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_COMPLETION_INVALID' USING ERRCODE = '22023';\n END IF;\n current_step := supacloud_workflows.lock_step_attempt(\n p_step_id, p_message_id, p_attempt, p_worker_id\n );\n SELECT * INTO current_run\n FROM supacloud_workflows.runs\n WHERE id = current_step.run_id\n FOR UPDATE;\n\n IF current_step.status = 'completed' THEN\n IF current_step.next_step_key IS NOT NULL\n OR current_step.output IS DISTINCT FROM p_step_output\n OR current_run.status IS DISTINCT FROM 'completed'\n OR current_run.output IS DISTINCT FROM p_run_output THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_IDEMPOTENCY_CONFLICT' USING ERRCODE = '23505';\n END IF;\n RETURN supacloud_workflows.snapshot(current_step.run_id, true);\n END IF;\n IF current_step.status <> 'running' THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_STALE_ATTEMPT' USING ERRCODE = '40001';\n END IF;\n IF current_run.status <> 'running' THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_RUN_NOT_ACTIVE' USING ERRCODE = '55000';\n END IF;\n\n SELECT pgmq.archive('supacloud_internal_workflows', p_message_id) INTO archived;\n IF archived IS DISTINCT FROM true THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_QUEUE_MESSAGE_MISSING' USING ERRCODE = '55000';\n END IF;\n UPDATE supacloud_workflows.steps\n SET status = 'completed', output = p_step_output, error_message = '',\n completed_at = now(), updated_at = now()\n WHERE id = current_step.id;\n UPDATE supacloud_workflows.runs\n SET status = 'completed', output = p_run_output, error_message = '',\n completed_at = now(), updated_at = now(), row_version = row_version + 1\n WHERE id = current_step.run_id AND status = 'running';\n INSERT INTO supacloud_workflows.events (run_id, step_id, event_type, attempt)\n VALUES (current_step.run_id, current_step.id, 'step_completed', p_attempt);\n INSERT INTO supacloud_workflows.events (run_id, step_id, event_type, attempt)\n VALUES (current_step.run_id, current_step.id, 'run_completed', p_attempt);\n RETURN supacloud_workflows.snapshot(current_step.run_id, false);\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION supacloud_workflows.retry_step(\n p_step_id uuid,\n p_message_id bigint,\n p_attempt integer,\n p_worker_id text,\n p_error_message text,\n p_delay_seconds integer\n) RETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n current_step supacloud_workflows.steps%ROWTYPE;\n normalized_error text := nullif(btrim(p_error_message), '');\n normalized_worker_id text := nullif(btrim(p_worker_id), '');\n retry_receipt jsonb;\n queue_message_updated boolean;\n archived boolean;\nBEGIN\n IF normalized_error IS NULL OR char_length(normalized_error) > 4000\n OR p_delay_seconds NOT BETWEEN 0 AND 86400 THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_RETRY_INVALID' USING ERRCODE = '22023';\n END IF;\n current_step := supacloud_workflows.lock_step(p_step_id);\n SELECT event.details INTO retry_receipt\n FROM supacloud_workflows.events event\n WHERE event.step_id = current_step.id\n AND event.attempt = p_attempt\n AND event.event_type IN ('step_retried', 'step_dead_lettered')\n AND event.details ->> 'operation' = 'retry'\n ORDER BY event.id DESC\n LIMIT 1;\n IF FOUND THEN\n IF retry_receipt ->> 'messageId' IS DISTINCT FROM p_message_id::text\n OR retry_receipt ->> 'workerId' IS DISTINCT FROM normalized_worker_id\n OR retry_receipt ->> 'errorMessage' IS DISTINCT FROM normalized_error\n OR (retry_receipt ->> 'delaySeconds')::integer IS DISTINCT FROM p_delay_seconds THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_IDEMPOTENCY_CONFLICT' USING ERRCODE = '23505';\n END IF;\n retry_receipt := retry_receipt || jsonb_build_object(\n 'stepId', current_step.id,\n 'attempt', p_attempt\n );\n RETURN supacloud_workflows.snapshot(current_step.run_id, true)\n || jsonb_build_object('retryReceipt', retry_receipt);\n END IF;\n IF normalized_worker_id IS NULL\n OR p_message_id IS NULL\n OR p_attempt IS NULL\n OR current_step.queue_message_id IS DISTINCT FROM p_message_id\n OR current_step.attempts IS DISTINCT FROM p_attempt\n OR current_step.claimed_by IS DISTINCT FROM normalized_worker_id\n OR current_step.status <> 'running' THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_STALE_ATTEMPT' USING ERRCODE = '40001';\n END IF;\n\n IF current_step.attempts >= current_step.max_attempts THEN\n SELECT pgmq.archive('supacloud_internal_workflows', p_message_id) INTO archived;\n IF archived IS DISTINCT FROM true THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_QUEUE_MESSAGE_MISSING' USING ERRCODE = '55000';\n END IF;\n UPDATE supacloud_workflows.steps\n SET status = 'dead_lettered', error_message = normalized_error,\n completed_at = now(), updated_at = now()\n WHERE id = current_step.id;\n UPDATE supacloud_workflows.runs\n SET status = 'failed', error_message = normalized_error,\n completed_at = now(), updated_at = now(), row_version = row_version + 1\n WHERE id = current_step.run_id AND status = 'running';\n IF NOT FOUND THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_RUN_NOT_ACTIVE' USING ERRCODE = '55000';\n END IF;\n retry_receipt := jsonb_build_object(\n 'operation', 'retry',\n 'stepId', current_step.id,\n 'messageId', p_message_id::text,\n 'attempt', p_attempt,\n 'workerId', normalized_worker_id,\n 'errorMessage', normalized_error,\n 'delaySeconds', p_delay_seconds\n );\n INSERT INTO supacloud_workflows.events (\n run_id, step_id, event_type, attempt, details\n ) VALUES (\n current_step.run_id, current_step.id, 'step_dead_lettered', p_attempt, retry_receipt\n );\n RETURN supacloud_workflows.snapshot(current_step.run_id, false)\n || jsonb_build_object('retryReceipt', retry_receipt);\n END IF;\n\n SELECT EXISTS (\n SELECT 1 FROM pgmq.set_vt('supacloud_internal_workflows', p_message_id, p_delay_seconds)\n ) INTO queue_message_updated;\n IF queue_message_updated IS DISTINCT FROM true THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_QUEUE_MESSAGE_MISSING' USING ERRCODE = '55000';\n END IF;\n UPDATE supacloud_workflows.steps\n SET status = 'queued', error_message = normalized_error,\n retry_delay_seconds = p_delay_seconds, updated_at = now()\n WHERE id = current_step.id;\n retry_receipt := jsonb_build_object(\n 'operation', 'retry',\n 'stepId', current_step.id,\n 'messageId', p_message_id::text,\n 'attempt', p_attempt,\n 'workerId', normalized_worker_id,\n 'errorMessage', normalized_error,\n 'delaySeconds', p_delay_seconds\n );\n INSERT INTO supacloud_workflows.events (\n run_id, step_id, event_type, attempt, details\n ) VALUES (\n current_step.run_id, current_step.id, 'step_retried', p_attempt, retry_receipt\n );\n RETURN supacloud_workflows.snapshot(current_step.run_id, false)\n || jsonb_build_object('retryReceipt', retry_receipt);\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION supacloud_workflows.fail_step(\n p_step_id uuid,\n p_message_id bigint,\n p_attempt integer,\n p_worker_id text,\n p_error_message text\n) RETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n current_step supacloud_workflows.steps%ROWTYPE;\n current_run supacloud_workflows.runs%ROWTYPE;\n normalized_error text := nullif(btrim(p_error_message), '');\n archived boolean;\nBEGIN\n IF normalized_error IS NULL OR char_length(normalized_error) > 4000 THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_FAILURE_INVALID' USING ERRCODE = '22023';\n END IF;\n current_step := supacloud_workflows.lock_step_attempt(\n p_step_id, p_message_id, p_attempt, p_worker_id\n );\n SELECT * INTO current_run\n FROM supacloud_workflows.runs\n WHERE id = current_step.run_id\n FOR UPDATE;\n\n IF current_step.status = 'failed' THEN\n IF current_step.error_message IS DISTINCT FROM normalized_error\n OR current_run.status IS DISTINCT FROM 'failed'\n OR current_run.error_message IS DISTINCT FROM normalized_error THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_IDEMPOTENCY_CONFLICT' USING ERRCODE = '23505';\n END IF;\n RETURN supacloud_workflows.snapshot(current_step.run_id, true);\n END IF;\n IF current_step.status <> 'running' THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_STALE_ATTEMPT' USING ERRCODE = '40001';\n END IF;\n IF current_run.status <> 'running' THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_RUN_NOT_ACTIVE' USING ERRCODE = '55000';\n END IF;\n\n SELECT pgmq.archive('supacloud_internal_workflows', p_message_id) INTO archived;\n IF archived IS DISTINCT FROM true THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_QUEUE_MESSAGE_MISSING' USING ERRCODE = '55000';\n END IF;\n UPDATE supacloud_workflows.steps\n SET status = 'failed', error_message = normalized_error,\n completed_at = now(), updated_at = now()\n WHERE id = current_step.id;\n UPDATE supacloud_workflows.runs\n SET status = 'failed', error_message = normalized_error,\n completed_at = now(), updated_at = now(), row_version = row_version + 1\n WHERE id = current_step.run_id AND status = 'running';\n INSERT INTO supacloud_workflows.events (\n run_id, step_id, event_type, attempt, details\n ) VALUES (\n current_step.run_id, current_step.id, 'step_failed', p_attempt,\n jsonb_build_object('errorMessage', normalized_error)\n );\n RETURN supacloud_workflows.snapshot(current_step.run_id, false);\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION supacloud_workflows.cancel_run(\n p_run_id uuid,\n p_reason text\n) RETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n normalized_reason text := nullif(btrim(p_reason), '');\n locked_run supacloud_workflows.runs%ROWTYPE;\n active_step supacloud_workflows.steps%ROWTYPE;\nBEGIN\n IF p_run_id IS NULL OR normalized_reason IS NULL OR char_length(normalized_reason) > 4000 THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_CANCEL_INVALID' USING ERRCODE = '22023';\n END IF;\n PERFORM pg_advisory_xact_lock(hashtextextended(p_run_id::text, 0));\n SELECT * INTO locked_run FROM supacloud_workflows.runs WHERE id = p_run_id FOR UPDATE;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_RUN_NOT_FOUND' USING ERRCODE = 'P0002';\n END IF;\n IF locked_run.status = 'cancelled' THEN\n IF locked_run.error_message IS DISTINCT FROM normalized_reason THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_IDEMPOTENCY_CONFLICT' USING ERRCODE = '23505';\n END IF;\n RETURN supacloud_workflows.snapshot(p_run_id, true);\n END IF;\n IF locked_run.status NOT IN ('queued', 'running') THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_RUN_NOT_ACTIVE' USING ERRCODE = '55000';\n END IF;\n SELECT * INTO active_step\n FROM supacloud_workflows.steps\n WHERE run_id = p_run_id AND status IN ('queued', 'running')\n FOR UPDATE;\n IF FOUND THEN\n PERFORM pgmq.archive('supacloud_internal_workflows', active_step.queue_message_id);\n UPDATE supacloud_workflows.steps\n SET status = 'cancelled', error_message = normalized_reason,\n completed_at = now(), updated_at = now()\n WHERE id = active_step.id;\n END IF;\n UPDATE supacloud_workflows.runs\n SET status = 'cancelled', error_message = normalized_reason,\n completed_at = now(), updated_at = now(), row_version = row_version + 1\n WHERE id = p_run_id;\n INSERT INTO supacloud_workflows.events (\n run_id, step_id, event_type, details\n ) VALUES (\n p_run_id, active_step.id, 'run_cancelled',\n jsonb_build_object('reason', normalized_reason)\n );\n RETURN supacloud_workflows.snapshot(p_run_id, false);\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION supacloud_workflows.run_events(\n p_run_id uuid,\n p_after_event_id bigint,\n p_limit integer\n) RETURNS jsonb\nLANGUAGE sql STABLE SECURITY DEFINER SET search_path = '' AS $$\n SELECT coalesce(jsonb_agg(jsonb_build_object(\n 'eventId', page.id::text,\n 'runId', page.run_id,\n 'stepId', page.step_id,\n 'eventType', page.event_type,\n 'attempt', page.attempt,\n 'details', page.details,\n 'createdAt', page.created_at\n ) ORDER BY page.id), '[]'::jsonb)\n FROM (\n SELECT event.*\n FROM supacloud_workflows.events event\n WHERE event.run_id = p_run_id AND event.id > p_after_event_id\n ORDER BY event.id\n LIMIT p_limit\n ) page\n$$;\n\nCREATE OR REPLACE FUNCTION supacloud_workflows.request_uuid(\n request jsonb,\n key text\n) RETURNS uuid\nLANGUAGE plpgsql IMMUTABLE SET search_path = '' AS $$\nDECLARE\n uuid_text text;\nBEGIN\n IF jsonb_typeof(request) IS DISTINCT FROM 'object' THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_REQUEST_INVALID' USING ERRCODE = '22023';\n END IF;\n uuid_text := request ->> key;\n IF uuid_text IS NULL\n OR uuid_text !~* '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_REQUEST_INVALID' USING ERRCODE = '22023';\n END IF;\n RETURN uuid_text::uuid;\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION public.supacloud_workflow_start(request jsonb)\nRETURNS jsonb\nLANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n max_attempts integer;\nBEGIN\n max_attempts := coalesce((request ->> 'maxAttempts')::integer, 3);\n RETURN supacloud_workflows.start_run(\n supacloud_workflows.request_uuid(request, 'runId'),\n request ->> 'workflowName',\n request ->> 'workflowVersion',\n request ->> 'firstStepKey',\n coalesce(request -> 'input', '{}'::jsonb),\n max_attempts\n );\nEXCEPTION\n WHEN invalid_parameter_value OR invalid_text_representation OR numeric_value_out_of_range THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_START_INVALID' USING ERRCODE = '22023';\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION public.supacloud_workflow_claim(request jsonb)\nRETURNS jsonb\nLANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n visibility_timeout_seconds integer;\nBEGIN\n IF jsonb_typeof(request) IS DISTINCT FROM 'object' THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_CLAIM_INVALID' USING ERRCODE = '22023';\n END IF;\n visibility_timeout_seconds := coalesce((request ->> 'visibilityTimeoutSeconds')::integer, 300);\n RETURN supacloud_workflows.claim_step(\n request ->> 'workerId', visibility_timeout_seconds\n );\nEXCEPTION\n WHEN invalid_parameter_value OR invalid_text_representation OR numeric_value_out_of_range THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_CLAIM_INVALID' USING ERRCODE = '22023';\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION public.supacloud_workflow_advance(request jsonb)\nRETURNS jsonb\nLANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n message_id bigint;\n attempt integer;\n next_max_attempts integer;\nBEGIN\n message_id := (request ->> 'messageId')::bigint;\n attempt := (request ->> 'attempt')::integer;\n next_max_attempts := coalesce((request ->> 'nextMaxAttempts')::integer, 3);\n IF message_id <= 0 OR attempt <= 0 THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_ADVANCE_INVALID' USING ERRCODE = '22023';\n END IF;\n RETURN supacloud_workflows.advance_step(\n supacloud_workflows.request_uuid(request, 'stepId'),\n message_id,\n attempt,\n request ->> 'workerId',\n coalesce(request -> 'output', '{}'::jsonb),\n request ->> 'nextStepKey',\n coalesce(request -> 'nextInput', '{}'::jsonb),\n next_max_attempts\n );\nEXCEPTION\n WHEN invalid_parameter_value OR invalid_text_representation OR numeric_value_out_of_range THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_ADVANCE_INVALID' USING ERRCODE = '22023';\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION public.supacloud_workflow_complete(request jsonb)\nRETURNS jsonb\nLANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n message_id bigint;\n attempt integer;\nBEGIN\n message_id := (request ->> 'messageId')::bigint;\n attempt := (request ->> 'attempt')::integer;\n IF message_id <= 0 OR attempt <= 0 THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_COMPLETION_INVALID' USING ERRCODE = '22023';\n END IF;\n RETURN supacloud_workflows.complete_run(\n supacloud_workflows.request_uuid(request, 'stepId'),\n message_id,\n attempt,\n request ->> 'workerId',\n coalesce(request -> 'stepOutput', '{}'::jsonb),\n coalesce(request -> 'runOutput', '{}'::jsonb)\n );\nEXCEPTION\n WHEN invalid_parameter_value OR invalid_text_representation OR numeric_value_out_of_range THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_COMPLETION_INVALID' USING ERRCODE = '22023';\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION public.supacloud_workflow_retry(request jsonb)\nRETURNS jsonb\nLANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n message_id bigint;\n attempt integer;\n delay_seconds integer;\nBEGIN\n message_id := (request ->> 'messageId')::bigint;\n attempt := (request ->> 'attempt')::integer;\n delay_seconds := coalesce((request ->> 'delaySeconds')::integer, 0);\n IF message_id <= 0 OR attempt <= 0 THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_RETRY_INVALID' USING ERRCODE = '22023';\n END IF;\n RETURN supacloud_workflows.retry_step(\n supacloud_workflows.request_uuid(request, 'stepId'),\n message_id,\n attempt,\n request ->> 'workerId',\n request ->> 'errorMessage',\n delay_seconds\n );\nEXCEPTION\n WHEN invalid_parameter_value OR invalid_text_representation OR numeric_value_out_of_range THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_RETRY_INVALID' USING ERRCODE = '22023';\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION public.supacloud_workflow_fail(request jsonb)\nRETURNS jsonb\nLANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n message_id bigint;\n attempt integer;\nBEGIN\n message_id := (request ->> 'messageId')::bigint;\n attempt := (request ->> 'attempt')::integer;\n IF message_id <= 0 OR attempt <= 0 THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_FAILURE_INVALID' USING ERRCODE = '22023';\n END IF;\n RETURN supacloud_workflows.fail_step(\n supacloud_workflows.request_uuid(request, 'stepId'),\n message_id,\n attempt,\n request ->> 'workerId',\n request ->> 'errorMessage'\n );\nEXCEPTION\n WHEN invalid_parameter_value OR invalid_text_representation OR numeric_value_out_of_range THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_FAILURE_INVALID' USING ERRCODE = '22023';\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION public.supacloud_workflow_cancel(request jsonb)\nRETURNS jsonb\nLANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path = '' AS $$\nBEGIN\n RETURN supacloud_workflows.cancel_run(\n supacloud_workflows.request_uuid(request, 'runId'),\n request ->> 'reason'\n );\nEXCEPTION\n WHEN invalid_parameter_value OR invalid_text_representation THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_CANCEL_INVALID' USING ERRCODE = '22023';\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION public.supacloud_workflow_get(request jsonb)\nRETURNS jsonb\nLANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = '' AS $$\nBEGIN\n RETURN supacloud_workflows.snapshot(\n supacloud_workflows.request_uuid(request, 'runId'), false\n );\nEXCEPTION\n WHEN invalid_parameter_value OR invalid_text_representation THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_GET_INVALID' USING ERRCODE = '22023';\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION public.supacloud_workflow_events(request jsonb)\nRETURNS jsonb\nLANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n after_event_id bigint;\n event_limit integer;\nBEGIN\n after_event_id := coalesce((request ->> 'afterEventId')::bigint, 0);\n event_limit := coalesce((request ->> 'limit')::integer, 100);\n IF after_event_id < 0 OR event_limit NOT BETWEEN 1 AND 500 THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_EVENTS_INVALID' USING ERRCODE = '22023';\n END IF;\n RETURN supacloud_workflows.run_events(\n supacloud_workflows.request_uuid(request, 'runId'), after_event_id, event_limit\n );\nEXCEPTION\n WHEN invalid_parameter_value OR invalid_text_representation OR numeric_value_out_of_range THEN\n RAISE EXCEPTION 'SUPACLOUD_WORKFLOW_EVENTS_INVALID' USING ERRCODE = '22023';\nEND;\n$$;\n\nREVOKE ALL ON ALL TABLES IN SCHEMA supacloud_workflows\n FROM PUBLIC, anon, authenticated, service_role;\nREVOKE ALL ON ALL SEQUENCES IN SCHEMA supacloud_workflows\n FROM PUBLIC, anon, authenticated, service_role;\nREVOKE ALL ON ALL FUNCTIONS IN SCHEMA supacloud_workflows\n FROM PUBLIC, anon, authenticated, service_role;\n\nREVOKE ALL ON FUNCTION public.supacloud_workflow_start(jsonb) FROM PUBLIC, anon, authenticated;\nREVOKE ALL ON FUNCTION public.supacloud_workflow_claim(jsonb) FROM PUBLIC, anon, authenticated;\nREVOKE ALL ON FUNCTION public.supacloud_workflow_advance(jsonb) FROM PUBLIC, anon, authenticated;\nREVOKE ALL ON FUNCTION public.supacloud_workflow_complete(jsonb) FROM PUBLIC, anon, authenticated;\nREVOKE ALL ON FUNCTION public.supacloud_workflow_retry(jsonb) FROM PUBLIC, anon, authenticated;\nREVOKE ALL ON FUNCTION public.supacloud_workflow_fail(jsonb) FROM PUBLIC, anon, authenticated;\nREVOKE ALL ON FUNCTION public.supacloud_workflow_cancel(jsonb) FROM PUBLIC, anon, authenticated;\nREVOKE ALL ON FUNCTION public.supacloud_workflow_get(jsonb) FROM PUBLIC, anon, authenticated;\nREVOKE ALL ON FUNCTION public.supacloud_workflow_events(jsonb) FROM PUBLIC, anon, authenticated;\n\nGRANT EXECUTE ON FUNCTION public.supacloud_workflow_start(jsonb) TO service_role;\nGRANT EXECUTE ON FUNCTION public.supacloud_workflow_claim(jsonb) TO service_role;\nGRANT EXECUTE ON FUNCTION public.supacloud_workflow_advance(jsonb) TO service_role;\nGRANT EXECUTE ON FUNCTION public.supacloud_workflow_complete(jsonb) TO service_role;\nGRANT EXECUTE ON FUNCTION public.supacloud_workflow_retry(jsonb) TO service_role;\nGRANT EXECUTE ON FUNCTION public.supacloud_workflow_fail(jsonb) TO service_role;\nGRANT EXECUTE ON FUNCTION public.supacloud_workflow_cancel(jsonb) TO service_role;\nGRANT EXECUTE ON FUNCTION public.supacloud_workflow_get(jsonb) TO service_role;\nGRANT EXECUTE ON FUNCTION public.supacloud_workflow_events(jsonb) TO service_role;\n-- supacloud:sql-module:workflows-public:end\n\n-- supacloud:sql-module:commands-public:start\nCREATE SCHEMA IF NOT EXISTS supacloud_commands;\nREVOKE ALL ON SCHEMA supacloud_commands FROM PUBLIC, anon, authenticated;\nGRANT USAGE ON SCHEMA supacloud_commands TO service_role;\n\nCREATE TABLE IF NOT EXISTS supacloud_commands.receipts (\n id uuid PRIMARY KEY,\n command_type text NOT NULL\n CHECK (command_type ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$'),\n target_type text NOT NULL\n CHECK (target_type ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$'),\n target_id text NOT NULL CHECK (char_length(target_id) BETWEEN 1 AND 500),\n actor_id uuid,\n payload jsonb NOT NULL DEFAULT '{}'::jsonb\n CHECK (jsonb_typeof(payload) = 'object'),\n payload_fingerprint text NOT NULL CHECK (char_length(payload_fingerprint) = 36),\n workflow_run_id uuid NOT NULL UNIQUE\n REFERENCES supacloud_workflows.runs(id) ON DELETE RESTRICT,\n created_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE INDEX IF NOT EXISTS supacloud_commands_target_idx\n ON supacloud_commands.receipts (target_type, target_id, created_at DESC, id);\n\nALTER TABLE supacloud_commands.receipts ADD COLUMN IF NOT EXISTS tenant_id text;\n\nCREATE OR REPLACE FUNCTION supacloud_commands.snapshot(\n p_command_id uuid,\n p_idempotent boolean DEFAULT false\n) RETURNS jsonb\nLANGUAGE sql STABLE SECURITY DEFINER SET search_path = '' AS $$\n SELECT jsonb_build_object(\n 'commandId', receipt.id,\n 'commandType', receipt.command_type,\n 'targetType', receipt.target_type,\n 'targetId', receipt.target_id,\n 'actorId', receipt.actor_id,\n 'tenantId', receipt.tenant_id,\n 'payloadFingerprint', receipt.payload_fingerprint,\n 'createdAt', receipt.created_at,\n 'idempotent', p_idempotent,\n 'workflow', supacloud_workflows.snapshot(receipt.workflow_run_id, false)\n )\n FROM supacloud_commands.receipts receipt\n WHERE receipt.id = p_command_id\n$$;\n\n-- Application-owned SECURITY DEFINER RPCs may call this after their domain\n-- mutation. PostgreSQL commits the domain write, receipt, and workflow enqueue\n-- together, so a lost response can be replayed with the same command ID.\nDROP FUNCTION IF EXISTS supacloud_commands.submit(uuid, text, text, text, uuid, jsonb, integer);\nCREATE OR REPLACE FUNCTION supacloud_commands.submit(request jsonb)\nRETURNS jsonb\nLANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n command_id_text text;\n command_id uuid;\n actor_id uuid;\n max_attempts integer;\n payload jsonb;\n normalized_command_type text;\n normalized_target_type text;\n normalized_target_id text;\n fingerprint text;\n existing supacloud_commands.receipts%ROWTYPE;\n tenant_id text;\n existing_execution jsonb;\nBEGIN\n IF jsonb_typeof(request) IS DISTINCT FROM 'object' THEN\n RAISE EXCEPTION 'SUPACLOUD_COMMAND_INVALID' USING ERRCODE = '22023';\n END IF;\n command_id_text := request ->> 'commandId';\n IF command_id_text IS NULL\n 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\n RAISE EXCEPTION 'SUPACLOUD_COMMAND_INVALID' USING ERRCODE = '22023';\n END IF;\n command_id := command_id_text::uuid;\n tenant_id := request->>'tenantId';\n IF request ? 'tenantId' AND (jsonb_typeof(request->'tenantId') IS DISTINCT FROM 'string'\n OR tenant_id !~ '^[A-Za-z0-9._:@|-]{1,200}$') THEN\n RAISE EXCEPTION 'SUPACLOUD_COMMAND_INVALID' USING ERRCODE = '22023';\n END IF;\n IF request ? 'actorId' AND request ->> 'actorId' IS NOT NULL THEN\n actor_id := (request ->> 'actorId')::uuid;\n END IF;\n max_attempts := coalesce((request ->> 'maxAttempts')::integer, 3);\n payload := coalesce(request -> 'payload', '{}'::jsonb);\n normalized_command_type := nullif(btrim(request ->> 'commandType'), '');\n normalized_target_type := nullif(btrim(request ->> 'targetType'), '');\n normalized_target_id := nullif(btrim(request ->> 'targetId'), '');\n IF normalized_command_type IS NULL\n OR normalized_command_type !~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$'\n OR normalized_target_type IS NULL\n OR normalized_target_type !~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$'\n OR normalized_target_id IS NULL\n OR char_length(normalized_target_id) > 500\n OR jsonb_typeof(payload) IS DISTINCT FROM 'object'\n OR max_attempts NOT BETWEEN 1 AND 100 THEN\n RAISE EXCEPTION 'SUPACLOUD_COMMAND_INVALID' USING ERRCODE = '22023';\n END IF;\n\n fingerprint := 'md5:' || md5(payload::text);\n PERFORM pg_advisory_xact_lock(hashtextextended(command_id::text, 0));\n SELECT * INTO existing FROM supacloud_commands.receipts WHERE id = command_id;\n IF FOUND THEN\n IF existing.command_type <> normalized_command_type\n OR existing.target_type <> normalized_target_type\n OR existing.target_id <> normalized_target_id\n OR existing.actor_id IS DISTINCT FROM actor_id\n OR existing.tenant_id IS DISTINCT FROM tenant_id\n OR existing.payload <> payload\n OR existing.payload_fingerprint <> fingerprint THEN\n RAISE EXCEPTION 'SUPACLOUD_COMMAND_IDEMPOTENCY_CONFLICT' USING ERRCODE = '23505';\n END IF;\n RETURN supacloud_commands.snapshot(command_id, true);\n END IF;\n IF to_regprocedure('supacloud_commands.execution_status(jsonb)') IS NOT NULL THEN\n EXECUTE 'SELECT supacloud_commands.execution_status($1)' INTO existing_execution\n USING jsonb_build_object('commandId',command_id);\n IF existing_execution IS NOT NULL THEN\n RAISE EXCEPTION 'SUPACLOUD_COMMAND_IDEMPOTENCY_CONFLICT' USING ERRCODE = '23505';\n END IF;\n END IF;\n\n PERFORM supacloud_workflows.start_run(\n command_id,\n 'command.' || normalized_command_type,\n '1',\n 'execute',\n jsonb_build_object(\n 'commandId', command_id,\n 'commandType', normalized_command_type,\n 'targetType', normalized_target_type,\n 'targetId', normalized_target_id,\n 'actorId', actor_id,\n 'payload', payload,\n 'payloadFingerprint', fingerprint\n ),\n max_attempts\n );\n\n INSERT INTO supacloud_commands.receipts (\n id, command_type, target_type, target_id, actor_id, payload,\n payload_fingerprint, workflow_run_id, tenant_id\n ) VALUES (\n command_id, normalized_command_type, normalized_target_type,\n normalized_target_id, actor_id, payload, fingerprint, command_id, tenant_id\n );\n RETURN supacloud_commands.snapshot(command_id, false);\nEXCEPTION\n WHEN invalid_parameter_value OR invalid_text_representation OR numeric_value_out_of_range THEN\n RAISE EXCEPTION 'SUPACLOUD_COMMAND_INVALID' USING ERRCODE = '22023';\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION supacloud_commands.status(request jsonb)\nRETURNS jsonb\nLANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n command_id_text text;\n execution jsonb;\n submission jsonb;\n field text;\nBEGIN\n IF jsonb_typeof(request) IS DISTINCT FROM 'object' THEN\n RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';\n END IF;\n IF request ? 'commandId' THEN\n IF jsonb_typeof(request->'commandId') IS DISTINCT FROM 'string'\n OR request->>'commandId' !~* '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'\n OR EXISTS(SELECT FROM jsonb_object_keys(request) AS k WHERE k <> 'commandId') THEN\n RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';\n END IF;\n request := jsonb_build_object('commandId',(request->>'commandId')::uuid::text);\n ELSE\n IF EXISTS(SELECT FROM jsonb_object_keys(request) AS k WHERE k NOT IN ('tenantId','actorId','command','operationId')) THEN\n RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';\n END IF;\n FOREACH field IN ARRAY ARRAY['tenantId','actorId','command','operationId'] LOOP\n IF jsonb_typeof(request->field) IS DISTINCT FROM 'string' OR request->>field !~ '^[A-Za-z0-9._:@|-]{1,200}$' THEN\n RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';\n END IF;\n END LOOP;\n END IF;\n IF to_regprocedure('supacloud_commands.execution_status(jsonb)') IS NOT NULL THEN\n EXECUTE 'SELECT supacloud_commands.execution_status($1)' INTO execution USING request;\n IF execution IS NOT NULL THEN RETURN execution; END IF;\n END IF;\n IF NOT (request ? 'commandId') THEN\n IF jsonb_typeof(request->'tenantId') IS DISTINCT FROM 'string'\n OR jsonb_typeof(request->'actorId') IS DISTINCT FROM 'string'\n OR jsonb_typeof(request->'command') IS DISTINCT FROM 'string'\n OR jsonb_typeof(request->'operationId') IS DISTINCT FROM 'string' THEN\n RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';\n END IF;\n RETURN NULL;\n END IF;\n command_id_text := request ->> 'commandId';\n IF command_id_text IS NULL\n 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\n RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';\n END IF;\n submission := supacloud_commands.snapshot(command_id_text::uuid, false);\n IF submission IS NULL THEN RETURN NULL; END IF;\n RETURN jsonb_build_object('kind','submission','commandId',command_id_text::uuid,\n 'execution',NULL,'workflow',jsonb_build_object(\n 'runId',submission->'workflow'->'runId','status',submission->'workflow'->'status'));\nEXCEPTION\n WHEN invalid_parameter_value OR invalid_text_representation THEN\n RAISE EXCEPTION 'SUPACLOUD_COMMAND_GET_INVALID' USING ERRCODE = '22023';\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION public.supacloud_command_submit(request jsonb)\nRETURNS jsonb\nLANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path = '' AS $$\nBEGIN\n PERFORM supacloud_commands.submit(request);\n RETURN supacloud_commands.status(jsonb_build_object('commandId',request->>'commandId'));\nEND\n$$;\n\nCREATE OR REPLACE FUNCTION public.supacloud_command_get(request jsonb)\nRETURNS jsonb\nLANGUAGE sql STABLE SECURITY DEFINER SET search_path = '' AS $$\n SELECT supacloud_commands.status(request)\n$$;\n\nREVOKE ALL ON ALL TABLES IN SCHEMA supacloud_commands\n FROM PUBLIC, anon, authenticated, service_role;\nREVOKE ALL ON ALL FUNCTIONS IN SCHEMA supacloud_commands\n FROM PUBLIC, anon, authenticated, service_role;\nREVOKE ALL ON FUNCTION public.supacloud_command_submit(jsonb) FROM PUBLIC, anon, authenticated;\nREVOKE ALL ON FUNCTION public.supacloud_command_get(jsonb) FROM PUBLIC, anon, authenticated;\nGRANT EXECUTE ON FUNCTION public.supacloud_command_submit(jsonb) TO service_role;\nGRANT EXECUTE ON FUNCTION public.supacloud_command_get(jsonb) TO service_role;\n-- supacloud:sql-module:commands-public:end\n\n-- supacloud:sql-module:artifacts-public:start\nCREATE SCHEMA IF NOT EXISTS supacloud_artifacts;\nREVOKE ALL ON SCHEMA supacloud_artifacts FROM PUBLIC, anon, authenticated;\nGRANT USAGE ON SCHEMA supacloud_artifacts TO service_role;\n\nCREATE TABLE IF NOT EXISTS supacloud_artifacts.artifacts (\n id uuid PRIMARY KEY,\n storage_object_id uuid NOT NULL UNIQUE REFERENCES storage.objects(id) ON DELETE RESTRICT,\n bucket_id text NOT NULL,\n object_path text NOT NULL,\n object_version text NOT NULL,\n artifact_type text NOT NULL\n CHECK (artifact_type ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$'),\n sha256 text NOT NULL CHECK (sha256 ~ '^[0-9a-f]{64}$'),\n size_bytes bigint NOT NULL CHECK (size_bytes >= 0),\n mime_type text NOT NULL CHECK (char_length(mime_type) BETWEEN 1 AND 255),\n metadata jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(metadata) = 'object'),\n retention_until timestamptz,\n created_by uuid,\n created_at timestamptz NOT NULL DEFAULT now(),\n CHECK (retention_until IS NULL OR retention_until >= created_at),\n UNIQUE (bucket_id, object_path, object_version)\n);\n\nCREATE INDEX IF NOT EXISTS supacloud_artifacts_lookup_idx\n ON supacloud_artifacts.artifacts (artifact_type, created_at DESC, id);\n\nCREATE TABLE IF NOT EXISTS supacloud_artifacts.lineage (\n parent_artifact_id uuid NOT NULL REFERENCES supacloud_artifacts.artifacts(id) ON DELETE RESTRICT,\n child_artifact_id uuid NOT NULL REFERENCES supacloud_artifacts.artifacts(id) ON DELETE RESTRICT,\n relation_type text NOT NULL\n CHECK (relation_type ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$'),\n metadata jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(metadata) = 'object'),\n created_at timestamptz NOT NULL DEFAULT now(),\n PRIMARY KEY (parent_artifact_id, child_artifact_id, relation_type),\n CHECK (parent_artifact_id <> child_artifact_id)\n);\n\nCREATE INDEX IF NOT EXISTS supacloud_artifacts_lineage_child_idx\n ON supacloud_artifacts.lineage (child_artifact_id, created_at, parent_artifact_id);\n\nCREATE OR REPLACE FUNCTION supacloud_artifacts.guard_storage_object()\nRETURNS trigger\nLANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$\nBEGIN\n IF EXISTS (\n SELECT 1 FROM supacloud_artifacts.artifacts artifact\n WHERE artifact.storage_object_id = OLD.id\n ) THEN\n RAISE EXCEPTION 'SUPACLOUD_ARTIFACT_IMMUTABLE' USING ERRCODE = '55000';\n END IF;\n RETURN CASE WHEN TG_OP = 'DELETE' THEN OLD ELSE NEW END;\nEND;\n$$;\n\nDROP TRIGGER IF EXISTS supacloud_artifact_storage_fence ON storage.objects;\nCREATE TRIGGER supacloud_artifact_storage_fence\nBEFORE UPDATE OR DELETE ON storage.objects\nFOR EACH ROW EXECUTE FUNCTION supacloud_artifacts.guard_storage_object();\n\nCREATE OR REPLACE FUNCTION supacloud_artifacts.snapshot(\n p_artifact_id uuid,\n p_idempotent boolean DEFAULT false\n) RETURNS jsonb\nLANGUAGE sql STABLE SECURITY DEFINER SET search_path = '' AS $$\n SELECT jsonb_build_object(\n 'artifactId', artifact.id,\n 'bucketId', artifact.bucket_id,\n 'objectPath', artifact.object_path,\n 'objectVersion', artifact.object_version,\n 'artifactType', artifact.artifact_type,\n 'sha256', artifact.sha256,\n 'sizeBytes', artifact.size_bytes::text,\n 'mimeType', artifact.mime_type,\n 'metadata', artifact.metadata,\n 'retentionUntil', artifact.retention_until,\n 'createdBy', artifact.created_by,\n 'createdAt', artifact.created_at,\n 'idempotent', p_idempotent,\n 'parents', coalesce((\n SELECT jsonb_agg(jsonb_build_object(\n 'artifactId', edge.parent_artifact_id,\n 'relationType', edge.relation_type,\n 'metadata', edge.metadata,\n 'createdAt', edge.created_at\n ) ORDER BY edge.created_at, edge.parent_artifact_id)\n FROM supacloud_artifacts.lineage edge\n WHERE edge.child_artifact_id = artifact.id\n ), '[]'::jsonb)\n )\n FROM supacloud_artifacts.artifacts artifact\n WHERE artifact.id = p_artifact_id\n$$;\n\nDROP FUNCTION IF EXISTS supacloud_artifacts.register(uuid, text, text, text, text, bigint, text, jsonb, timestamptz, uuid);\nCREATE OR REPLACE FUNCTION supacloud_artifacts.register(request jsonb)\nRETURNS jsonb\nLANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n artifact_id uuid;\n size_bytes bigint;\n metadata jsonb;\n retention_until timestamptz;\n created_by uuid;\n normalized_bucket text;\n normalized_path text;\n normalized_type text;\n normalized_sha256 text;\n normalized_mime text;\n object_row storage.objects%ROWTYPE;\n existing supacloud_artifacts.artifacts%ROWTYPE;\nBEGIN\n IF jsonb_typeof(request) IS DISTINCT FROM 'object' THEN\n RAISE EXCEPTION 'SUPACLOUD_ARTIFACT_INVALID' USING ERRCODE = '22023';\n END IF;\n artifact_id := (request ->> 'artifactId')::uuid;\n size_bytes := (request ->> 'sizeBytes')::bigint;\n metadata := coalesce(request -> 'metadata', '{}'::jsonb);\n retention_until := (request ->> 'retentionUntil')::timestamptz;\n created_by := (request ->> 'createdBy')::uuid;\n normalized_bucket := nullif(btrim(request ->> 'bucketId'), '');\n normalized_path := nullif(btrim(request ->> 'objectPath'), '');\n normalized_type := nullif(btrim(request ->> 'artifactType'), '');\n normalized_sha256 := lower(nullif(btrim(request ->> 'sha256'), ''));\n normalized_mime := lower(nullif(btrim(request ->> 'mimeType'), ''));\n IF artifact_id IS NULL OR normalized_bucket IS NULL OR normalized_path IS NULL\n OR normalized_path LIKE '/%' OR normalized_path LIKE '%\\%'\n OR normalized_path ~ '(^|/)(.|..)(/|$)'\n OR normalized_type IS NULL\n OR normalized_type !~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$'\n OR normalized_sha256 IS NULL OR normalized_sha256 !~ '^[0-9a-f]{64}$'\n OR size_bytes IS NULL OR size_bytes < 0\n OR normalized_mime IS NULL OR char_length(normalized_mime) > 255\n OR jsonb_typeof(metadata) IS DISTINCT FROM 'object'\n OR (retention_until IS NOT NULL AND retention_until < now()) THEN\n RAISE EXCEPTION 'SUPACLOUD_ARTIFACT_INVALID' USING ERRCODE = '22023';\n END IF;\n\n SELECT * INTO object_row FROM storage.objects\n WHERE bucket_id = normalized_bucket AND name = normalized_path;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'SUPACLOUD_ARTIFACT_OBJECT_NOT_FOUND' USING ERRCODE = 'P0002';\n END IF;\n\n PERFORM pg_advisory_xact_lock(hashtextextended(artifact_id::text, 0));\n SELECT * INTO existing FROM supacloud_artifacts.artifacts WHERE id = artifact_id;\n IF FOUND THEN\n IF existing.storage_object_id <> object_row.id\n OR existing.artifact_type <> normalized_type\n OR existing.sha256 <> normalized_sha256\n OR existing.size_bytes <> size_bytes\n OR existing.mime_type <> normalized_mime\n OR existing.metadata <> metadata\n OR existing.retention_until IS DISTINCT FROM retention_until\n OR existing.created_by IS DISTINCT FROM created_by THEN\n RAISE EXCEPTION 'SUPACLOUD_ARTIFACT_IDEMPOTENCY_CONFLICT' USING ERRCODE = '23505';\n END IF;\n RETURN supacloud_artifacts.snapshot(artifact_id, true);\n END IF;\n\n INSERT INTO supacloud_artifacts.artifacts (\n id, storage_object_id, bucket_id, object_path, object_version,\n artifact_type, sha256, size_bytes, mime_type, metadata,\n retention_until, created_by\n ) VALUES (\n artifact_id, object_row.id, normalized_bucket, normalized_path,\n object_row.version::text, normalized_type, normalized_sha256,\n size_bytes, normalized_mime, metadata, retention_until, created_by\n );\n RETURN supacloud_artifacts.snapshot(artifact_id, false);\nEXCEPTION\n WHEN invalid_parameter_value OR invalid_text_representation OR numeric_value_out_of_range THEN\n RAISE EXCEPTION 'SUPACLOUD_ARTIFACT_INVALID' USING ERRCODE = '22023';\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION supacloud_artifacts.link(\n p_parent_artifact_id uuid,\n p_child_artifact_id uuid,\n p_relation_type text,\n p_metadata jsonb\n) RETURNS jsonb\nLANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path = '' AS $$\nDECLARE\n normalized_relation text := nullif(btrim(p_relation_type), '');\n inserted_count integer;\nBEGIN\n IF p_parent_artifact_id IS NULL OR p_child_artifact_id IS NULL\n OR p_parent_artifact_id = p_child_artifact_id\n OR normalized_relation IS NULL\n OR normalized_relation !~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$'\n OR jsonb_typeof(p_metadata) IS DISTINCT FROM 'object' THEN\n RAISE EXCEPTION 'SUPACLOUD_ARTIFACT_LINEAGE_INVALID' USING ERRCODE = '22023';\n END IF;\n IF EXISTS (\n WITH RECURSIVE descendants(artifact_id) AS (\n SELECT edge.child_artifact_id\n FROM supacloud_artifacts.lineage edge\n WHERE edge.parent_artifact_id = p_child_artifact_id\n UNION\n SELECT edge.child_artifact_id\n FROM supacloud_artifacts.lineage edge\n JOIN descendants current ON edge.parent_artifact_id = current.artifact_id\n )\n SELECT 1 FROM descendants WHERE artifact_id = p_parent_artifact_id\n ) THEN\n RAISE EXCEPTION 'SUPACLOUD_ARTIFACT_LINEAGE_CYCLE' USING ERRCODE = '23514';\n END IF;\n INSERT INTO supacloud_artifacts.lineage (\n parent_artifact_id, child_artifact_id, relation_type, metadata\n ) VALUES (\n p_parent_artifact_id, p_child_artifact_id, normalized_relation, p_metadata\n ) ON CONFLICT DO NOTHING;\n GET DIAGNOSTICS inserted_count = ROW_COUNT;\n IF inserted_count = 0 AND NOT EXISTS (\n SELECT 1 FROM supacloud_artifacts.lineage\n WHERE parent_artifact_id = p_parent_artifact_id\n AND child_artifact_id = p_child_artifact_id\n AND relation_type = normalized_relation\n AND metadata = p_metadata\n ) THEN\n RAISE EXCEPTION 'SUPACLOUD_ARTIFACT_IDEMPOTENCY_CONFLICT' USING ERRCODE = '23505';\n END IF;\n RETURN supacloud_artifacts.snapshot(p_child_artifact_id, inserted_count = 0);\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION public.supacloud_artifact_register(request jsonb)\nRETURNS jsonb\nLANGUAGE sql VOLATILE SECURITY DEFINER SET search_path = '' AS $$\n SELECT supacloud_artifacts.register(request)\n$$;\n\nCREATE OR REPLACE FUNCTION public.supacloud_artifact_get(request jsonb)\nRETURNS jsonb\nLANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = '' AS $$\nBEGIN\n RETURN supacloud_artifacts.snapshot((request ->> 'artifactId')::uuid, false);\nEXCEPTION\n WHEN invalid_parameter_value OR invalid_text_representation THEN\n RAISE EXCEPTION 'SUPACLOUD_ARTIFACT_GET_INVALID' USING ERRCODE = '22023';\nEND;\n$$;\n\nCREATE OR REPLACE FUNCTION public.supacloud_artifact_link(request jsonb)\nRETURNS jsonb\nLANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path = '' AS $$\nBEGIN\n RETURN supacloud_artifacts.link(\n (request ->> 'parentArtifactId')::uuid,\n (request ->> 'childArtifactId')::uuid,\n request ->> 'relationType',\n coalesce(request -> 'metadata', '{}'::jsonb)\n );\nEXCEPTION\n WHEN invalid_parameter_value OR invalid_text_representation THEN\n RAISE EXCEPTION 'SUPACLOUD_ARTIFACT_LINEAGE_INVALID' USING ERRCODE = '22023';\nEND;\n$$;\n\nREVOKE ALL ON ALL TABLES IN SCHEMA supacloud_artifacts\n FROM PUBLIC, anon, authenticated, service_role;\nREVOKE ALL ON ALL FUNCTIONS IN SCHEMA supacloud_artifacts\n FROM PUBLIC, anon, authenticated, service_role;\nREVOKE ALL ON FUNCTION public.supacloud_artifact_register(jsonb) FROM PUBLIC, anon, authenticated;\nREVOKE ALL ON FUNCTION public.supacloud_artifact_get(jsonb) FROM PUBLIC, anon, authenticated;\nREVOKE ALL ON FUNCTION public.supacloud_artifact_link(jsonb) FROM PUBLIC, anon, authenticated;\nGRANT EXECUTE ON FUNCTION public.supacloud_artifact_register(jsonb) TO service_role;\nGRANT EXECUTE ON FUNCTION public.supacloud_artifact_get(jsonb) TO service_role;\nGRANT EXECUTE ON FUNCTION public.supacloud_artifact_link(jsonb) TO service_role;\n-- supacloud:sql-module:artifacts-public:end\n";
|
|
18
23
|
/**
|
|
19
24
|
* pg_cron emulation: the `cron.job` / `cron.job_run_details` tables and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emulated.d.ts","sourceRoot":"","sources":["../../../src/runtime/db/emulated.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"emulated.d.ts","sourceRoot":"","sources":["../../../src/runtime/db/emulated.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH;;;;GAIG;AACH,eAAO,MAAM,aAAa,uwRAyLzB,CAAA;AAED;gFACgF;AAChF,eAAO,MAAM,eAAe,uuWAkT3B,CAAA;AAED,eAAO,MAAM,QAAQ,2+nBACF,CAAA;AAEnB,eAAO,MAAM,aAAa,4mgEAwjDzB,CAAA;AAID;;;;GAIG;AACH,eAAO,MAAM,QAAQ,uyEA6DpB,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,OAAO,82GA0EnB,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,ulBAqB1B,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,SAAS,m/FAoErB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pglite-engine.d.ts","sourceRoot":"","sources":["../../../src/runtime/db/pglite-engine.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAA8C,MAAM,aAAa,CAAA;AA2BvF;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"pglite-engine.d.ts","sourceRoot":"","sources":["../../../src/runtime/db/pglite-engine.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAA8C,MAAM,aAAa,CAAA;AA2BvF;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CA8IrH"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { DbEngine } from '../../db/engine.js';
|
|
2
|
+
export type ExtensionInventorySource = 'pglite-bundled' | 'native-postgresql';
|
|
3
|
+
export type ExtensionInventoryStatus = 'inspected' | 'unavailable';
|
|
4
|
+
export interface PostgresExtension {
|
|
5
|
+
name: string;
|
|
6
|
+
default_version: string | null;
|
|
7
|
+
installed_version: string | null;
|
|
8
|
+
comment: string | null;
|
|
9
|
+
}
|
|
10
|
+
export interface PostgresExtensionInventory {
|
|
11
|
+
source: ExtensionInventorySource;
|
|
12
|
+
status: ExtensionInventoryStatus;
|
|
13
|
+
available: PostgresExtension[];
|
|
14
|
+
installed: Array<{
|
|
15
|
+
name: string;
|
|
16
|
+
version: string;
|
|
17
|
+
}>;
|
|
18
|
+
reason?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function inspectPostgresExtensions(engine: DbEngine, source: ExtensionInventorySource): Promise<PostgresExtensionInventory>;
|
|
21
|
+
//# sourceMappingURL=extensions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extensions.d.ts","sourceRoot":"","sources":["../../../../src/runtime/node/native/extensions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAElD,MAAM,MAAM,wBAAwB,GAAG,gBAAgB,GAAG,mBAAmB,CAAA;AAC7E,MAAM,MAAM,wBAAwB,GAAG,WAAW,GAAG,aAAa,CAAA;AAElE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,wBAAwB,CAAA;IAChC,MAAM,EAAE,wBAAwB,CAAA;IAChC,SAAS,EAAE,iBAAiB,EAAE,CAAA;IAC9B,SAAS,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACnD,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,0BAA0B,CAAC,CA6BrC"}
|
|
@@ -2,6 +2,7 @@ import type { DbEngine } from '../../db/engine.js';
|
|
|
2
2
|
import type { GraphqlCapability } from '../../graphql.js';
|
|
3
3
|
import type { RuntimeMode } from '../../functions/profile.js';
|
|
4
4
|
import { type PowerSyncReplicationOptions } from './replication.js';
|
|
5
|
+
import type { PostgresExtensionInventory } from './extensions.js';
|
|
5
6
|
export type LiteCapabilityStatus = 'supported' | 'static' | 'disabled' | 'unsupported';
|
|
6
7
|
export interface LiteDoctorReport {
|
|
7
8
|
graphql: GraphqlCapability;
|
|
@@ -22,6 +23,9 @@ export interface LiteDoctorReport {
|
|
|
22
23
|
commands: 'supported';
|
|
23
24
|
artifacts: 'supported';
|
|
24
25
|
postgrest_schema_config: 'static';
|
|
26
|
+
postgres_extensions: PostgresExtensionInventory & {
|
|
27
|
+
engine: 'pglite' | 'native';
|
|
28
|
+
};
|
|
25
29
|
logical_replication: LiteCapabilityStatus;
|
|
26
30
|
powersync_source: LiteCapabilityStatus;
|
|
27
31
|
replication_profile?: 'powersync';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readiness.d.ts","sourceRoot":"","sources":["../../../../src/runtime/node/native/readiness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA;AAC7D,OAAO,EAGL,KAAK,2BAA2B,EACjC,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"readiness.d.ts","sourceRoot":"","sources":["../../../../src/runtime/node/native/readiness.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const STANDALONE_PGLITE_ASSETS: unique symbol;
|
|
2
|
-
export type StandaloneExtensionName = 'uuid_ossp' | 'pgcrypto' | 'citext' | 'pg_trgm' | 'ltree' | 'hstore' | 'fuzzystrmatch';
|
|
2
|
+
export type StandaloneExtensionName = 'uuid_ossp' | 'pgcrypto' | 'citext' | 'pg_trgm' | 'ltree' | 'hstore' | 'fuzzystrmatch' | 'vector' | 'postgis' | 'pg_textsearch' | 'pgmq';
|
|
3
3
|
export interface PreparedExtensionBundles {
|
|
4
4
|
bundles: Record<StandaloneExtensionName, URL>;
|
|
5
5
|
cleanup: () => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standalone-assets-protocol.d.ts","sourceRoot":"","sources":["../src/standalone-assets-protocol.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,wBAAwB,eAAwD,CAAA;AAE7F,MAAM,MAAM,uBAAuB,GAC/B,WAAW,GACX,UAAU,GACV,QAAQ,GACR,SAAS,GACT,OAAO,GACP,QAAQ,GACR,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"standalone-assets-protocol.d.ts","sourceRoot":"","sources":["../src/standalone-assets-protocol.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,wBAAwB,eAAwD,CAAA;AAE7F,MAAM,MAAM,uBAAuB,GAC/B,WAAW,GACX,UAAU,GACV,QAAQ,GACR,SAAS,GACT,OAAO,GACP,QAAQ,GACR,eAAe,GACf,QAAQ,GACR,SAAS,GACT,eAAe,GACf,MAAM,CAAA;AAEV,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAA;IAC7C,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACrC,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAA;IACpC,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAA;IACpC,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAA;IACrB,uBAAuB,EAAE,MAAM,OAAO,CAAC,wBAAwB,CAAC,CAAA;CACjE"}
|