dzql 0.5.33 → 0.6.1
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/.env.sample +28 -0
- package/compose.yml +28 -0
- package/dist/client/index.ts +1 -0
- package/dist/client/stores/useMyProfileStore.ts +114 -0
- package/dist/client/stores/useOrgDashboardStore.ts +131 -0
- package/dist/client/stores/useVenueDetailStore.ts +117 -0
- package/dist/client/ws.ts +716 -0
- package/dist/db/migrations/000_core.sql +92 -0
- package/dist/db/migrations/20251229T212912022Z_schema.sql +3020 -0
- package/dist/db/migrations/20251229T212912022Z_subscribables.sql +371 -0
- package/dist/runtime/manifest.json +1562 -0
- package/docs/README.md +309 -36
- package/docs/feature-requests/applyPatch-bug-report.md +85 -0
- package/docs/feature-requests/connection-ready-profile.md +57 -0
- package/docs/feature-requests/hidden-bug-report.md +111 -0
- package/docs/feature-requests/hidden-fields-subscribables.md +34 -0
- package/docs/feature-requests/subscribable-param-key-bug.md +38 -0
- package/docs/feature-requests/todo.md +146 -0
- package/docs/for_ai.md +653 -0
- package/docs/project-setup.md +456 -0
- package/examples/blog.ts +50 -0
- package/examples/invalid.ts +18 -0
- package/examples/venues.js +485 -0
- package/package.json +23 -60
- package/src/cli/codegen/client.ts +99 -0
- package/src/cli/codegen/manifest.ts +95 -0
- package/src/cli/codegen/pinia.ts +174 -0
- package/src/cli/codegen/realtime.ts +58 -0
- package/src/cli/codegen/sql.ts +698 -0
- package/src/cli/codegen/subscribable_sql.ts +547 -0
- package/src/cli/codegen/subscribable_store.ts +184 -0
- package/src/cli/codegen/types.ts +142 -0
- package/src/cli/compiler/analyzer.ts +52 -0
- package/src/cli/compiler/graph_rules.ts +251 -0
- package/src/cli/compiler/ir.ts +233 -0
- package/src/cli/compiler/loader.ts +132 -0
- package/src/cli/compiler/permissions.ts +227 -0
- package/src/cli/index.ts +166 -0
- package/src/client/index.ts +1 -0
- package/src/client/ws.ts +286 -0
- package/src/runtime/auth.ts +39 -0
- package/src/runtime/db.ts +33 -0
- package/src/runtime/errors.ts +51 -0
- package/src/runtime/index.ts +98 -0
- package/src/runtime/js_functions.ts +63 -0
- package/src/runtime/manifest_loader.ts +29 -0
- package/src/runtime/namespace.ts +483 -0
- package/src/runtime/server.ts +87 -0
- package/src/runtime/ws.ts +197 -0
- package/src/shared/ir.ts +197 -0
- package/tests/client.test.ts +38 -0
- package/tests/codegen.test.ts +71 -0
- package/tests/compiler.test.ts +45 -0
- package/tests/graph_rules.test.ts +173 -0
- package/tests/integration/db.test.ts +174 -0
- package/tests/integration/e2e.test.ts +65 -0
- package/tests/integration/features.test.ts +922 -0
- package/tests/integration/full_stack.test.ts +262 -0
- package/tests/integration/setup.ts +45 -0
- package/tests/ir.test.ts +32 -0
- package/tests/namespace.test.ts +395 -0
- package/tests/permissions.test.ts +55 -0
- package/tests/pinia.test.ts +48 -0
- package/tests/realtime.test.ts +22 -0
- package/tests/runtime.test.ts +80 -0
- package/tests/subscribable_gen.test.ts +72 -0
- package/tests/subscribable_reactivity.test.ts +258 -0
- package/tests/venues_gen.test.ts +25 -0
- package/tsconfig.json +20 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/README.md +0 -90
- package/bin/cli.js +0 -727
- package/docs/compiler/ADVANCED_FILTERS.md +0 -183
- package/docs/compiler/CODING_STANDARDS.md +0 -415
- package/docs/compiler/COMPARISON.md +0 -673
- package/docs/compiler/QUICKSTART.md +0 -326
- package/docs/compiler/README.md +0 -134
- package/docs/examples/README.md +0 -38
- package/docs/examples/blog.sql +0 -160
- package/docs/examples/venue-detail-simple.sql +0 -8
- package/docs/examples/venue-detail-subscribable.sql +0 -45
- package/docs/for-ai/claude-guide.md +0 -1210
- package/docs/getting-started/quickstart.md +0 -125
- package/docs/getting-started/subscriptions-quick-start.md +0 -203
- package/docs/getting-started/tutorial.md +0 -1104
- package/docs/guides/atomic-updates.md +0 -299
- package/docs/guides/client-stores.md +0 -730
- package/docs/guides/composite-primary-keys.md +0 -158
- package/docs/guides/custom-functions.md +0 -362
- package/docs/guides/drop-semantics.md +0 -554
- package/docs/guides/field-defaults.md +0 -240
- package/docs/guides/interpreter-vs-compiler.md +0 -237
- package/docs/guides/many-to-many.md +0 -929
- package/docs/guides/subscriptions.md +0 -537
- package/docs/reference/api.md +0 -1373
- package/docs/reference/client.md +0 -224
- package/src/client/stores/index.js +0 -8
- package/src/client/stores/useAppStore.js +0 -285
- package/src/client/stores/useWsStore.js +0 -289
- package/src/client/ws.js +0 -762
- package/src/compiler/cli/compile-example.js +0 -33
- package/src/compiler/cli/compile-subscribable.js +0 -43
- package/src/compiler/cli/debug-compile.js +0 -44
- package/src/compiler/cli/debug-parse.js +0 -26
- package/src/compiler/cli/debug-path-parser.js +0 -18
- package/src/compiler/cli/debug-subscribable-parser.js +0 -21
- package/src/compiler/cli/index.js +0 -174
- package/src/compiler/codegen/auth-codegen.js +0 -153
- package/src/compiler/codegen/drop-semantics-codegen.js +0 -553
- package/src/compiler/codegen/graph-rules-codegen.js +0 -450
- package/src/compiler/codegen/notification-codegen.js +0 -232
- package/src/compiler/codegen/operation-codegen.js +0 -1382
- package/src/compiler/codegen/permission-codegen.js +0 -318
- package/src/compiler/codegen/subscribable-codegen.js +0 -827
- package/src/compiler/compiler.js +0 -371
- package/src/compiler/index.js +0 -11
- package/src/compiler/parser/entity-parser.js +0 -440
- package/src/compiler/parser/path-parser.js +0 -290
- package/src/compiler/parser/subscribable-parser.js +0 -244
- package/src/database/dzql-core.sql +0 -161
- package/src/database/migrations/001_schema.sql +0 -60
- package/src/database/migrations/002_functions.sql +0 -890
- package/src/database/migrations/003_operations.sql +0 -1135
- package/src/database/migrations/004_search.sql +0 -581
- package/src/database/migrations/005_entities.sql +0 -730
- package/src/database/migrations/006_auth.sql +0 -94
- package/src/database/migrations/007_events.sql +0 -133
- package/src/database/migrations/008_hello.sql +0 -18
- package/src/database/migrations/008a_meta.sql +0 -172
- package/src/database/migrations/009_subscriptions.sql +0 -240
- package/src/database/migrations/010_atomic_updates.sql +0 -157
- package/src/database/migrations/010_fix_m2m_events.sql +0 -94
- package/src/index.js +0 -40
- package/src/server/api.js +0 -9
- package/src/server/db.js +0 -442
- package/src/server/index.js +0 -317
- package/src/server/logger.js +0 -259
- package/src/server/mcp.js +0 -594
- package/src/server/meta-route.js +0 -251
- package/src/server/namespace.js +0 -292
- package/src/server/subscriptions.js +0 -351
- package/src/server/ws.js +0 -573
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
-- Authentication System
|
|
2
|
-
-- Simple users table with login/register/profile functions
|
|
3
|
-
|
|
4
|
-
-- Enable pgcrypto extension for password hashing
|
|
5
|
-
create extension if not exists pgcrypto;
|
|
6
|
-
|
|
7
|
-
-- === Users Table ===
|
|
8
|
-
-- Minimal auth table - applications can add columns via migrations
|
|
9
|
-
-- Note: created_at is tracked via the action log, not here
|
|
10
|
-
create table if not exists users (
|
|
11
|
-
id serial primary key,
|
|
12
|
-
email text unique not null,
|
|
13
|
-
password_hash text not null
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
-- === Auth Functions ===
|
|
17
|
-
|
|
18
|
-
-- Register new user
|
|
19
|
-
-- p_options: optional JSON object with additional fields to set on the user record
|
|
20
|
-
-- Example: register_user('test@example.com', 'password', '{"name": "Test User"}')
|
|
21
|
-
create or replace function register_user(p_email text, p_password text, p_options jsonb default null)
|
|
22
|
-
returns jsonb
|
|
23
|
-
language plpgsql
|
|
24
|
-
security definer
|
|
25
|
-
as $$
|
|
26
|
-
declare
|
|
27
|
-
v_user_id int;
|
|
28
|
-
v_salt text;
|
|
29
|
-
v_hash text;
|
|
30
|
-
v_insert_data jsonb;
|
|
31
|
-
begin
|
|
32
|
-
-- Generate salt and hash password
|
|
33
|
-
v_salt := gen_salt('bf', 10);
|
|
34
|
-
v_hash := crypt(p_password, v_salt);
|
|
35
|
-
|
|
36
|
-
-- Build insert data: options fields + email + password_hash (options cannot override core fields)
|
|
37
|
-
v_insert_data := jsonb_build_object('email', p_email, 'password_hash', v_hash);
|
|
38
|
-
if p_options is not null then
|
|
39
|
-
v_insert_data := (p_options - 'id' - 'email' - 'password_hash' - 'password') || v_insert_data;
|
|
40
|
-
end if;
|
|
41
|
-
|
|
42
|
-
-- Dynamic INSERT from JSONB (same pattern as compiled save functions)
|
|
43
|
-
execute (
|
|
44
|
-
select format(
|
|
45
|
-
'INSERT INTO users (%s) VALUES (%s) RETURNING id',
|
|
46
|
-
string_agg(quote_ident(key), ', '),
|
|
47
|
-
string_agg(quote_nullable(value), ', ')
|
|
48
|
-
)
|
|
49
|
-
from jsonb_each_text(v_insert_data) kv(key, value)
|
|
50
|
-
) into v_user_id;
|
|
51
|
-
|
|
52
|
-
return _profile(v_user_id);
|
|
53
|
-
exception
|
|
54
|
-
when unique_violation then
|
|
55
|
-
raise exception 'Email already exists' using errcode = '23505';
|
|
56
|
-
end $$;
|
|
57
|
-
|
|
58
|
-
-- Login user
|
|
59
|
-
create or replace function login_user(p_email text, p_password text)
|
|
60
|
-
returns jsonb
|
|
61
|
-
language plpgsql
|
|
62
|
-
security definer
|
|
63
|
-
as $$
|
|
64
|
-
declare
|
|
65
|
-
user_record record;
|
|
66
|
-
begin
|
|
67
|
-
select id, email, password_hash
|
|
68
|
-
into user_record
|
|
69
|
-
from users
|
|
70
|
-
where email = p_email;
|
|
71
|
-
|
|
72
|
-
if not found then
|
|
73
|
-
raise exception 'Invalid credentials' using errcode = '28000';
|
|
74
|
-
end if;
|
|
75
|
-
|
|
76
|
-
if not (user_record.password_hash = crypt(p_password, user_record.password_hash)) then
|
|
77
|
-
raise exception 'Invalid credentials' using errcode = '28000';
|
|
78
|
-
end if;
|
|
79
|
-
|
|
80
|
-
return _profile(user_record.id);
|
|
81
|
-
end $$;
|
|
82
|
-
|
|
83
|
-
-- Get user profile (private function)
|
|
84
|
-
-- Returns all user columns except sensitive fields (password_hash, password, secret, token)
|
|
85
|
-
-- This allows the users table to have any additional columns without modifying this function
|
|
86
|
-
create or replace function _profile(p_user_id int)
|
|
87
|
-
returns jsonb
|
|
88
|
-
language sql
|
|
89
|
-
security definer
|
|
90
|
-
as $$
|
|
91
|
-
select jsonb_build_object('user_id', u.id) || (to_jsonb(u.*) - 'id' - 'password_hash' - 'password' - 'secret' - 'token')
|
|
92
|
-
from users u
|
|
93
|
-
where id = p_user_id;
|
|
94
|
-
$$;
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
-- DZQL Framework - Event System and Real-time Notifications
|
|
2
|
-
-- Events are created ONLY by API operations (generic_save, generic_delete)
|
|
3
|
-
-- This ensures proper user context and security
|
|
4
|
-
|
|
5
|
-
-- ============================================================================
|
|
6
|
-
-- NOTIFY EVENT FUNCTION
|
|
7
|
-
-- ============================================================================
|
|
8
|
-
-- Event notification trigger - handles all real-time notifications
|
|
9
|
-
CREATE OR REPLACE FUNCTION dzql.notify_event()
|
|
10
|
-
RETURNS TRIGGER LANGUAGE plpgsql AS $$
|
|
11
|
-
BEGIN
|
|
12
|
-
-- Send real-time notification to single channel
|
|
13
|
-
PERFORM pg_notify('dzql', jsonb_build_object(
|
|
14
|
-
'event_id', NEW.event_id,
|
|
15
|
-
'table', NEW.table_name,
|
|
16
|
-
'op', NEW.op,
|
|
17
|
-
'pk', NEW.pk,
|
|
18
|
-
'data', NEW.data,
|
|
19
|
-
'user_id', NEW.user_id,
|
|
20
|
-
'at', NEW.at,
|
|
21
|
-
'notify_users', NEW.notify_users
|
|
22
|
-
)::text);
|
|
23
|
-
|
|
24
|
-
RETURN NULL;
|
|
25
|
-
END $$;
|
|
26
|
-
|
|
27
|
-
-- ============================================================================
|
|
28
|
-
-- CREATE TRIGGER ON EVENTS TABLE
|
|
29
|
-
-- ============================================================================
|
|
30
|
-
-- Create trigger on events table to handle notifications
|
|
31
|
-
DROP TRIGGER IF EXISTS dzql_events_notify ON dzql.events;
|
|
32
|
-
CREATE TRIGGER dzql_events_notify
|
|
33
|
-
AFTER INSERT ON dzql.events
|
|
34
|
-
FOR EACH ROW EXECUTE FUNCTION dzql.notify_event();
|
|
35
|
-
|
|
36
|
-
-- ============================================================================
|
|
37
|
-
-- HELPER FUNCTIONS
|
|
38
|
-
-- ============================================================================
|
|
39
|
-
|
|
40
|
-
-- Get event history for a specific record (audit trail)
|
|
41
|
-
CREATE OR REPLACE FUNCTION dzql.get_record_history(
|
|
42
|
-
p_table_name text,
|
|
43
|
-
p_record_id text,
|
|
44
|
-
p_limit int DEFAULT 50
|
|
45
|
-
) RETURNS jsonb
|
|
46
|
-
LANGUAGE sql STABLE AS $$
|
|
47
|
-
SELECT COALESCE(jsonb_agg(
|
|
48
|
-
to_jsonb(e) ORDER BY e.at DESC
|
|
49
|
-
), '[]'::jsonb)
|
|
50
|
-
FROM (
|
|
51
|
-
SELECT * FROM dzql.events e
|
|
52
|
-
WHERE e.table_name = p_table_name
|
|
53
|
-
AND e.pk->>'id' = p_record_id
|
|
54
|
-
ORDER BY e.at DESC
|
|
55
|
-
LIMIT p_limit
|
|
56
|
-
) e;
|
|
57
|
-
$$;
|
|
58
|
-
|
|
59
|
-
-- Get recent actions by a user
|
|
60
|
-
CREATE OR REPLACE FUNCTION dzql.get_user_actions(
|
|
61
|
-
p_user_id int,
|
|
62
|
-
p_limit int DEFAULT 100
|
|
63
|
-
) RETURNS jsonb
|
|
64
|
-
LANGUAGE sql STABLE AS $$
|
|
65
|
-
SELECT COALESCE(jsonb_agg(
|
|
66
|
-
to_jsonb(e) ORDER BY e.at DESC
|
|
67
|
-
), '[]'::jsonb)
|
|
68
|
-
FROM (
|
|
69
|
-
SELECT * FROM dzql.events e
|
|
70
|
-
WHERE e.user_id = p_user_id
|
|
71
|
-
ORDER BY e.at DESC
|
|
72
|
-
LIMIT p_limit
|
|
73
|
-
) e;
|
|
74
|
-
$$;
|
|
75
|
-
|
|
76
|
-
-- Get event catchup data for synchronization
|
|
77
|
-
CREATE OR REPLACE FUNCTION dzql.catchup(p_context_id text, p_since_event_id bigint)
|
|
78
|
-
RETURNS jsonb LANGUAGE sql STABLE AS $$
|
|
79
|
-
SELECT coalesce(jsonb_agg(to_jsonb(e) order by e.event_id), '[]'::jsonb)
|
|
80
|
-
FROM dzql.events e
|
|
81
|
-
WHERE e.event_id > p_since_event_id;
|
|
82
|
-
$$;
|
|
83
|
-
|
|
84
|
-
-- Get single event by ID
|
|
85
|
-
CREATE OR REPLACE FUNCTION dzql.get_event(p_event_id bigint)
|
|
86
|
-
RETURNS jsonb LANGUAGE sql STABLE AS $$
|
|
87
|
-
SELECT to_jsonb(e) FROM dzql.events e WHERE e.event_id = p_event_id;
|
|
88
|
-
$$;
|
|
89
|
-
|
|
90
|
-
-- Get recent events on a table
|
|
91
|
-
CREATE OR REPLACE FUNCTION dzql.get_table_events(
|
|
92
|
-
p_table_name text,
|
|
93
|
-
p_limit int DEFAULT 100
|
|
94
|
-
) RETURNS jsonb
|
|
95
|
-
LANGUAGE sql STABLE AS $$
|
|
96
|
-
SELECT COALESCE(jsonb_agg(
|
|
97
|
-
to_jsonb(e) ORDER BY e.at DESC
|
|
98
|
-
), '[]'::jsonb)
|
|
99
|
-
FROM (
|
|
100
|
-
SELECT * FROM dzql.events e
|
|
101
|
-
WHERE e.table_name = p_table_name
|
|
102
|
-
ORDER BY e.at DESC
|
|
103
|
-
LIMIT p_limit
|
|
104
|
-
) e;
|
|
105
|
-
$$;
|
|
106
|
-
|
|
107
|
-
-- Comments
|
|
108
|
-
COMMENT ON FUNCTION dzql.notify_event() IS 'Broadcasts event notifications via PostgreSQL NOTIFY for real-time updates';
|
|
109
|
-
COMMENT ON FUNCTION dzql.get_record_history(text, text, int) IS 'Returns audit trail for a specific record';
|
|
110
|
-
COMMENT ON FUNCTION dzql.get_user_actions(int, int) IS 'Returns recent actions performed by a user';
|
|
111
|
-
COMMENT ON FUNCTION dzql.get_table_events(text, int) IS 'Returns recent events for a table';
|
|
112
|
-
|
|
113
|
-
-- ============================================================================
|
|
114
|
-
-- CLEANUP OLD TRIGGER SYSTEM
|
|
115
|
-
-- ============================================================================
|
|
116
|
-
-- Remove any table triggers that were created by the old system
|
|
117
|
-
DO $$
|
|
118
|
-
DECLARE
|
|
119
|
-
l_table_name text;
|
|
120
|
-
BEGIN
|
|
121
|
-
-- Remove old emit_row_change function if it exists
|
|
122
|
-
DROP FUNCTION IF EXISTS dzql.emit_row_change() CASCADE;
|
|
123
|
-
|
|
124
|
-
-- Clean up any existing table triggers from registered entities
|
|
125
|
-
FOR l_table_name IN
|
|
126
|
-
SELECT table_name FROM dzql.entities
|
|
127
|
-
LOOP
|
|
128
|
-
EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I',
|
|
129
|
-
l_table_name || '_dzql_events', l_table_name);
|
|
130
|
-
END LOOP;
|
|
131
|
-
|
|
132
|
-
RAISE NOTICE 'Cleaned up old table trigger system';
|
|
133
|
-
END $$;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
-- Hello World Function for Testing DZQL's Function Proxy
|
|
2
|
-
|
|
3
|
-
-- Create a simple hello world function
|
|
4
|
-
-- First parameter must be p_user_id for DZQL compatibility
|
|
5
|
-
create or replace function hello(p_user_id int, p_name text default 'World')
|
|
6
|
-
returns jsonb
|
|
7
|
-
language plpgsql
|
|
8
|
-
security definer
|
|
9
|
-
as $$
|
|
10
|
-
begin
|
|
11
|
-
return jsonb_build_object(
|
|
12
|
-
'message', 'Hello, ' || coalesce(p_name, 'World') || '!',
|
|
13
|
-
'timestamp', now(),
|
|
14
|
-
'from', 'PostgreSQL',
|
|
15
|
-
'user_id', p_user_id
|
|
16
|
-
);
|
|
17
|
-
end;
|
|
18
|
-
$$;
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
-- === Entity Metadata Function ===
|
|
2
|
-
-- Returns complete metadata for all registered entities in the DZQL system
|
|
3
|
-
-- Includes entity config, schema information, and relationship graph
|
|
4
|
-
-- Used by UI to dynamically configure based on registered entities
|
|
5
|
-
|
|
6
|
-
create or replace function get_entities_metadata(p_user_id int)
|
|
7
|
-
returns jsonb
|
|
8
|
-
language plpgsql
|
|
9
|
-
security definer
|
|
10
|
-
as $$
|
|
11
|
-
declare
|
|
12
|
-
v_entities jsonb;
|
|
13
|
-
v_relations jsonb;
|
|
14
|
-
v_junction_tables text[];
|
|
15
|
-
v_entity_names text[];
|
|
16
|
-
begin
|
|
17
|
-
-- Get list of registered entity names
|
|
18
|
-
select array_agg(table_name) into v_entity_names
|
|
19
|
-
from dzql.entities;
|
|
20
|
-
|
|
21
|
-
-- Build entities object with schema information
|
|
22
|
-
select jsonb_object_agg(
|
|
23
|
-
e.table_name,
|
|
24
|
-
jsonb_build_object(
|
|
25
|
-
'table_name', e.table_name,
|
|
26
|
-
'label_field', e.label_field,
|
|
27
|
-
'searchable_fields', e.searchable_fields,
|
|
28
|
-
'fk_includes', e.fk_includes,
|
|
29
|
-
'soft_delete', e.soft_delete,
|
|
30
|
-
'temporal_fields', e.temporal_fields,
|
|
31
|
-
'notification_paths', e.notification_paths,
|
|
32
|
-
'permission_paths', e.permission_paths,
|
|
33
|
-
'primary_key', (
|
|
34
|
-
-- Get primary key columns
|
|
35
|
-
select jsonb_agg(a.attname order by a.attnum)
|
|
36
|
-
from pg_index i
|
|
37
|
-
join pg_attribute a on a.attrelid = i.indrelid and a.attnum = any(i.indkey)
|
|
38
|
-
where i.indrelid = e.table_name::regclass and i.indisprimary
|
|
39
|
-
),
|
|
40
|
-
'schema', (
|
|
41
|
-
-- Get column schema from information_schema
|
|
42
|
-
select jsonb_agg(
|
|
43
|
-
jsonb_build_object(
|
|
44
|
-
'column_name', c.column_name,
|
|
45
|
-
'data_type', c.data_type,
|
|
46
|
-
'is_nullable', c.is_nullable = 'YES',
|
|
47
|
-
'column_default', c.column_default,
|
|
48
|
-
'character_maximum_length', c.character_maximum_length,
|
|
49
|
-
'numeric_precision', c.numeric_precision,
|
|
50
|
-
'numeric_scale', c.numeric_scale,
|
|
51
|
-
'ordinal_position', c.ordinal_position
|
|
52
|
-
) order by c.ordinal_position
|
|
53
|
-
)
|
|
54
|
-
from information_schema.columns c
|
|
55
|
-
where c.table_schema = 'public'
|
|
56
|
-
and c.table_name = e.table_name
|
|
57
|
-
)
|
|
58
|
-
)
|
|
59
|
-
) into v_entities
|
|
60
|
-
from dzql.entities e;
|
|
61
|
-
|
|
62
|
-
-- Identify junction tables (2+ foreign keys, minimal searchable fields)
|
|
63
|
-
select array_agg(distinct tc.table_name) into v_junction_tables
|
|
64
|
-
from information_schema.table_constraints tc
|
|
65
|
-
where tc.constraint_type = 'FOREIGN KEY'
|
|
66
|
-
and tc.table_schema = 'public'
|
|
67
|
-
and tc.table_name = any(v_entity_names)
|
|
68
|
-
group by tc.table_name
|
|
69
|
-
having count(*) >= 2
|
|
70
|
-
and (
|
|
71
|
-
select count(*)
|
|
72
|
-
from unnest((
|
|
73
|
-
select searchable_fields
|
|
74
|
-
from dzql.entities
|
|
75
|
-
where table_name = tc.table_name
|
|
76
|
-
)) as sf
|
|
77
|
-
where sf not in (
|
|
78
|
-
select kcu.column_name
|
|
79
|
-
from information_schema.key_column_usage kcu
|
|
80
|
-
where kcu.table_name = tc.table_name
|
|
81
|
-
and kcu.constraint_name in (
|
|
82
|
-
select constraint_name
|
|
83
|
-
from information_schema.table_constraints
|
|
84
|
-
where table_name = tc.table_name
|
|
85
|
-
and constraint_type = 'FOREIGN KEY'
|
|
86
|
-
)
|
|
87
|
-
)
|
|
88
|
-
) <= 1;
|
|
89
|
-
|
|
90
|
-
-- Build relations array
|
|
91
|
-
with fk_relations as (
|
|
92
|
-
-- Get all foreign key relationships
|
|
93
|
-
select
|
|
94
|
-
tc.table_name,
|
|
95
|
-
kcu.column_name,
|
|
96
|
-
ccu.table_name as foreign_table_name,
|
|
97
|
-
ccu.column_name as foreign_column_name
|
|
98
|
-
from information_schema.table_constraints tc
|
|
99
|
-
join information_schema.key_column_usage kcu
|
|
100
|
-
on tc.constraint_name = kcu.constraint_name
|
|
101
|
-
and tc.table_schema = kcu.table_schema
|
|
102
|
-
join information_schema.constraint_column_usage ccu
|
|
103
|
-
on ccu.constraint_name = tc.constraint_name
|
|
104
|
-
and ccu.table_schema = tc.table_schema
|
|
105
|
-
where tc.constraint_type = 'FOREIGN KEY'
|
|
106
|
-
and tc.table_schema = 'public'
|
|
107
|
-
and tc.table_name = any(v_entity_names)
|
|
108
|
-
and ccu.table_name = any(v_entity_names)
|
|
109
|
-
),
|
|
110
|
-
simple_relations as (
|
|
111
|
-
-- Many-to-one and one-to-many for non-junction tables
|
|
112
|
-
select jsonb_build_object(
|
|
113
|
-
'type', 'many_to_one',
|
|
114
|
-
'from', fk.table_name || '.' || fk.column_name,
|
|
115
|
-
'to', fk.foreign_table_name || '.' || fk.foreign_column_name
|
|
116
|
-
) as relation
|
|
117
|
-
from fk_relations fk
|
|
118
|
-
where not (fk.table_name = any(v_junction_tables))
|
|
119
|
-
|
|
120
|
-
union all
|
|
121
|
-
|
|
122
|
-
select jsonb_build_object(
|
|
123
|
-
'type', 'one_to_many',
|
|
124
|
-
'from', fk.foreign_table_name || '.' || fk.foreign_column_name,
|
|
125
|
-
'to', fk.table_name || '.' || fk.column_name
|
|
126
|
-
) as relation
|
|
127
|
-
from fk_relations fk
|
|
128
|
-
where not (fk.table_name = any(v_junction_tables))
|
|
129
|
-
),
|
|
130
|
-
junction_relations as (
|
|
131
|
-
-- Many-to-many through junction tables
|
|
132
|
-
select jsonb_build_object(
|
|
133
|
-
'type', 'many_to_many',
|
|
134
|
-
'from', fk1.foreign_table_name || '.' || fk1.foreign_column_name,
|
|
135
|
-
'to', fk2.foreign_table_name || '.' || fk2.foreign_column_name,
|
|
136
|
-
'via', fk1.table_name || '.' || fk1.column_name || '.' || fk2.column_name
|
|
137
|
-
) as relation
|
|
138
|
-
from fk_relations fk1
|
|
139
|
-
join fk_relations fk2
|
|
140
|
-
on fk1.table_name = fk2.table_name
|
|
141
|
-
and fk1.column_name < fk2.column_name -- avoid duplicates
|
|
142
|
-
where fk1.table_name = any(v_junction_tables)
|
|
143
|
-
|
|
144
|
-
union all
|
|
145
|
-
|
|
146
|
-
select jsonb_build_object(
|
|
147
|
-
'type', 'many_to_many',
|
|
148
|
-
'from', fk2.foreign_table_name || '.' || fk2.foreign_column_name,
|
|
149
|
-
'to', fk1.foreign_table_name || '.' || fk1.foreign_column_name,
|
|
150
|
-
'via', fk1.table_name || '.' || fk2.column_name || '.' || fk1.column_name
|
|
151
|
-
) as relation
|
|
152
|
-
from fk_relations fk1
|
|
153
|
-
join fk_relations fk2
|
|
154
|
-
on fk1.table_name = fk2.table_name
|
|
155
|
-
and fk1.column_name < fk2.column_name
|
|
156
|
-
where fk1.table_name = any(v_junction_tables)
|
|
157
|
-
)
|
|
158
|
-
select jsonb_agg(relation) into v_relations
|
|
159
|
-
from (
|
|
160
|
-
select relation from simple_relations
|
|
161
|
-
union all
|
|
162
|
-
select relation from junction_relations
|
|
163
|
-
) all_relations;
|
|
164
|
-
|
|
165
|
-
-- Return complete metadata structure
|
|
166
|
-
return jsonb_build_object(
|
|
167
|
-
'entities', v_entities,
|
|
168
|
-
'relations', coalesce(v_relations, '[]'::jsonb),
|
|
169
|
-
'operations', jsonb_build_array('get', 'save', 'delete', 'lookup', 'search')
|
|
170
|
-
);
|
|
171
|
-
end;
|
|
172
|
-
$$;
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
-- Migration 009: Live Query Subscriptions Infrastructure
|
|
2
|
-
-- Adds support for subscribable documents (Pattern 1 from vision.md)
|
|
3
|
-
|
|
4
|
-
-- ============================================================================
|
|
5
|
-
-- Subscribables Registry (Metadata Only)
|
|
6
|
-
-- ============================================================================
|
|
7
|
-
|
|
8
|
-
-- Stores metadata for registered subscribables
|
|
9
|
-
-- Note: Active subscriptions are held in-memory on the server
|
|
10
|
-
CREATE TABLE IF NOT EXISTS dzql.subscribables (
|
|
11
|
-
name TEXT PRIMARY KEY,
|
|
12
|
-
permission_paths JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
13
|
-
param_schema JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
14
|
-
root_entity TEXT, -- NULL allowed for dashboard mode (pure collections)
|
|
15
|
-
relations JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
16
|
-
scope_tables TEXT[] NOT NULL DEFAULT '{}',
|
|
17
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
18
|
-
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
19
|
-
);
|
|
20
|
-
|
|
21
|
-
COMMENT ON TABLE dzql.subscribables IS
|
|
22
|
-
'Registry of subscribable documents. Subscribables define denormalized views that clients can subscribe to for real-time updates.';
|
|
23
|
-
|
|
24
|
-
COMMENT ON COLUMN dzql.subscribables.name IS
|
|
25
|
-
'Subscribable identifier (used in subscribe_<name> RPC calls)';
|
|
26
|
-
|
|
27
|
-
COMMENT ON COLUMN dzql.subscribables.permission_paths IS
|
|
28
|
-
'Access control paths (e.g., {"subscribe": ["@org_id->acts_for[org_id=$]{active}.user_id"]})';
|
|
29
|
-
|
|
30
|
-
COMMENT ON COLUMN dzql.subscribables.param_schema IS
|
|
31
|
-
'Parameter schema defining subscription key (e.g., {"venue_id": "int"})';
|
|
32
|
-
|
|
33
|
-
COMMENT ON COLUMN dzql.subscribables.root_entity IS
|
|
34
|
-
'Root table for the subscribable document. NULL for dashboard mode (pure collections).';
|
|
35
|
-
|
|
36
|
-
COMMENT ON COLUMN dzql.subscribables.relations IS
|
|
37
|
-
'Related entities to include (e.g., {"org": "organisations", "sites": {"entity": "sites", "filter": "venue_id=$venue_id"}})';
|
|
38
|
-
|
|
39
|
-
COMMENT ON COLUMN dzql.subscribables.scope_tables IS
|
|
40
|
-
'Array of table names that are in scope for this subscribable (root + all relations). Used for efficient event filtering.';
|
|
41
|
-
|
|
42
|
-
-- Index for quick lookups
|
|
43
|
-
CREATE INDEX IF NOT EXISTS idx_subscribables_root_entity
|
|
44
|
-
ON dzql.subscribables(root_entity);
|
|
45
|
-
|
|
46
|
-
-- ============================================================================
|
|
47
|
-
-- Register Subscribable Function
|
|
48
|
-
-- ============================================================================
|
|
49
|
-
|
|
50
|
-
CREATE OR REPLACE FUNCTION dzql.register_subscribable(
|
|
51
|
-
p_name TEXT,
|
|
52
|
-
p_permission_paths JSONB,
|
|
53
|
-
p_param_schema JSONB,
|
|
54
|
-
p_root_entity TEXT,
|
|
55
|
-
p_relations JSONB
|
|
56
|
-
) RETURNS TEXT AS $$
|
|
57
|
-
DECLARE
|
|
58
|
-
v_result TEXT;
|
|
59
|
-
BEGIN
|
|
60
|
-
-- Validate inputs
|
|
61
|
-
IF p_name IS NULL OR p_name = '' THEN
|
|
62
|
-
RAISE EXCEPTION 'Subscribable name cannot be empty';
|
|
63
|
-
END IF;
|
|
64
|
-
|
|
65
|
-
IF p_root_entity IS NULL OR p_root_entity = '' THEN
|
|
66
|
-
RAISE EXCEPTION 'Root entity cannot be empty';
|
|
67
|
-
END IF;
|
|
68
|
-
|
|
69
|
-
-- Insert or update subscribable
|
|
70
|
-
INSERT INTO dzql.subscribables (
|
|
71
|
-
name,
|
|
72
|
-
permission_paths,
|
|
73
|
-
param_schema,
|
|
74
|
-
root_entity,
|
|
75
|
-
relations,
|
|
76
|
-
created_at,
|
|
77
|
-
updated_at
|
|
78
|
-
) VALUES (
|
|
79
|
-
p_name,
|
|
80
|
-
COALESCE(p_permission_paths, '{}'::jsonb),
|
|
81
|
-
COALESCE(p_param_schema, '{}'::jsonb),
|
|
82
|
-
p_root_entity,
|
|
83
|
-
COALESCE(p_relations, '{}'::jsonb),
|
|
84
|
-
NOW(),
|
|
85
|
-
NOW()
|
|
86
|
-
)
|
|
87
|
-
ON CONFLICT (name) DO UPDATE SET
|
|
88
|
-
permission_paths = EXCLUDED.permission_paths,
|
|
89
|
-
param_schema = EXCLUDED.param_schema,
|
|
90
|
-
root_entity = EXCLUDED.root_entity,
|
|
91
|
-
relations = EXCLUDED.relations,
|
|
92
|
-
updated_at = NOW();
|
|
93
|
-
|
|
94
|
-
v_result := format('Subscribable "%s" registered successfully', p_name);
|
|
95
|
-
|
|
96
|
-
RAISE NOTICE '%', v_result;
|
|
97
|
-
|
|
98
|
-
RETURN v_result;
|
|
99
|
-
END;
|
|
100
|
-
$$ LANGUAGE plpgsql;
|
|
101
|
-
|
|
102
|
-
COMMENT ON FUNCTION dzql.register_subscribable IS
|
|
103
|
-
'Register a subscribable document definition. Used by the compiler to store subscribable metadata.';
|
|
104
|
-
|
|
105
|
-
-- ============================================================================
|
|
106
|
-
-- Helper Functions
|
|
107
|
-
-- ============================================================================
|
|
108
|
-
|
|
109
|
-
-- Get all registered subscribables
|
|
110
|
-
CREATE OR REPLACE FUNCTION dzql.get_subscribables()
|
|
111
|
-
RETURNS TABLE (
|
|
112
|
-
name TEXT,
|
|
113
|
-
permission_paths JSONB,
|
|
114
|
-
param_schema JSONB,
|
|
115
|
-
root_entity TEXT,
|
|
116
|
-
relations JSONB,
|
|
117
|
-
scope_tables TEXT[],
|
|
118
|
-
created_at TIMESTAMPTZ,
|
|
119
|
-
updated_at TIMESTAMPTZ
|
|
120
|
-
) AS $$
|
|
121
|
-
BEGIN
|
|
122
|
-
RETURN QUERY
|
|
123
|
-
SELECT
|
|
124
|
-
s.name,
|
|
125
|
-
s.permission_paths,
|
|
126
|
-
s.param_schema,
|
|
127
|
-
s.root_entity,
|
|
128
|
-
s.relations,
|
|
129
|
-
s.scope_tables,
|
|
130
|
-
s.created_at,
|
|
131
|
-
s.updated_at
|
|
132
|
-
FROM dzql.subscribables s
|
|
133
|
-
ORDER BY s.name;
|
|
134
|
-
END;
|
|
135
|
-
$$ LANGUAGE plpgsql STABLE;
|
|
136
|
-
|
|
137
|
-
COMMENT ON FUNCTION dzql.get_subscribables IS
|
|
138
|
-
'Retrieve all registered subscribables';
|
|
139
|
-
|
|
140
|
-
-- Get subscribable by name
|
|
141
|
-
CREATE OR REPLACE FUNCTION dzql.get_subscribable(p_name TEXT)
|
|
142
|
-
RETURNS TABLE (
|
|
143
|
-
name TEXT,
|
|
144
|
-
permission_paths JSONB,
|
|
145
|
-
param_schema JSONB,
|
|
146
|
-
root_entity TEXT,
|
|
147
|
-
relations JSONB,
|
|
148
|
-
scope_tables TEXT[],
|
|
149
|
-
created_at TIMESTAMPTZ,
|
|
150
|
-
updated_at TIMESTAMPTZ
|
|
151
|
-
) AS $$
|
|
152
|
-
BEGIN
|
|
153
|
-
RETURN QUERY
|
|
154
|
-
SELECT
|
|
155
|
-
s.name,
|
|
156
|
-
s.permission_paths,
|
|
157
|
-
s.param_schema,
|
|
158
|
-
s.root_entity,
|
|
159
|
-
s.relations,
|
|
160
|
-
s.scope_tables,
|
|
161
|
-
s.created_at,
|
|
162
|
-
s.updated_at
|
|
163
|
-
FROM dzql.subscribables s
|
|
164
|
-
WHERE s.name = p_name;
|
|
165
|
-
END;
|
|
166
|
-
$$ LANGUAGE plpgsql STABLE;
|
|
167
|
-
|
|
168
|
-
COMMENT ON FUNCTION dzql.get_subscribable IS
|
|
169
|
-
'Retrieve a specific subscribable by name';
|
|
170
|
-
|
|
171
|
-
-- Get subscribables by root entity
|
|
172
|
-
CREATE OR REPLACE FUNCTION dzql.get_subscribables_by_entity(p_entity TEXT)
|
|
173
|
-
RETURNS TABLE (
|
|
174
|
-
name TEXT,
|
|
175
|
-
permission_paths JSONB,
|
|
176
|
-
param_schema JSONB,
|
|
177
|
-
root_entity TEXT,
|
|
178
|
-
relations JSONB,
|
|
179
|
-
scope_tables TEXT[],
|
|
180
|
-
created_at TIMESTAMPTZ,
|
|
181
|
-
updated_at TIMESTAMPTZ
|
|
182
|
-
) AS $$
|
|
183
|
-
BEGIN
|
|
184
|
-
RETURN QUERY
|
|
185
|
-
SELECT
|
|
186
|
-
s.name,
|
|
187
|
-
s.permission_paths,
|
|
188
|
-
s.param_schema,
|
|
189
|
-
s.root_entity,
|
|
190
|
-
s.relations,
|
|
191
|
-
s.scope_tables,
|
|
192
|
-
s.created_at,
|
|
193
|
-
s.updated_at
|
|
194
|
-
FROM dzql.subscribables s
|
|
195
|
-
WHERE s.root_entity = p_entity
|
|
196
|
-
ORDER BY s.name;
|
|
197
|
-
END;
|
|
198
|
-
$$ LANGUAGE plpgsql STABLE;
|
|
199
|
-
|
|
200
|
-
COMMENT ON FUNCTION dzql.get_subscribables_by_entity IS
|
|
201
|
-
'Retrieve all subscribables for a given root entity';
|
|
202
|
-
|
|
203
|
-
-- Delete subscribable
|
|
204
|
-
CREATE OR REPLACE FUNCTION dzql.delete_subscribable(p_name TEXT)
|
|
205
|
-
RETURNS TEXT AS $$
|
|
206
|
-
DECLARE
|
|
207
|
-
v_result TEXT;
|
|
208
|
-
BEGIN
|
|
209
|
-
DELETE FROM dzql.subscribables
|
|
210
|
-
WHERE name = p_name;
|
|
211
|
-
|
|
212
|
-
IF FOUND THEN
|
|
213
|
-
v_result := format('Subscribable "%s" deleted successfully', p_name);
|
|
214
|
-
ELSE
|
|
215
|
-
v_result := format('Subscribable "%s" not found', p_name);
|
|
216
|
-
END IF;
|
|
217
|
-
|
|
218
|
-
RAISE NOTICE '%', v_result;
|
|
219
|
-
|
|
220
|
-
RETURN v_result;
|
|
221
|
-
END;
|
|
222
|
-
$$ LANGUAGE plpgsql;
|
|
223
|
-
|
|
224
|
-
COMMENT ON FUNCTION dzql.delete_subscribable IS
|
|
225
|
-
'Delete a subscribable by name';
|
|
226
|
-
|
|
227
|
-
-- ============================================================================
|
|
228
|
-
-- Verification
|
|
229
|
-
-- ============================================================================
|
|
230
|
-
|
|
231
|
-
DO $$
|
|
232
|
-
BEGIN
|
|
233
|
-
RAISE NOTICE 'Migration 009: Live Query Subscriptions - Complete';
|
|
234
|
-
RAISE NOTICE 'Subscribables table created';
|
|
235
|
-
RAISE NOTICE 'Helper functions installed';
|
|
236
|
-
RAISE NOTICE '';
|
|
237
|
-
RAISE NOTICE 'Usage:';
|
|
238
|
-
RAISE NOTICE ' SELECT dzql.register_subscribable(''name'', permissions, params, root, relations);';
|
|
239
|
-
RAISE NOTICE ' SELECT * FROM dzql.get_subscribables();';
|
|
240
|
-
END $$;
|