dzql 0.5.33 → 0.6.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.
Files changed (150) hide show
  1. package/.env.sample +28 -0
  2. package/compose.yml +28 -0
  3. package/dist/client/index.ts +1 -0
  4. package/dist/client/stores/useMyProfileStore.ts +114 -0
  5. package/dist/client/stores/useOrgDashboardStore.ts +131 -0
  6. package/dist/client/stores/useVenueDetailStore.ts +117 -0
  7. package/dist/client/ws.ts +716 -0
  8. package/dist/db/migrations/000_core.sql +92 -0
  9. package/dist/db/migrations/20251229T212912022Z_schema.sql +3020 -0
  10. package/dist/db/migrations/20251229T212912022Z_subscribables.sql +371 -0
  11. package/dist/runtime/manifest.json +1562 -0
  12. package/docs/README.md +293 -36
  13. package/docs/feature-requests/applyPatch-bug-report.md +85 -0
  14. package/docs/feature-requests/connection-ready-profile.md +57 -0
  15. package/docs/feature-requests/hidden-bug-report.md +111 -0
  16. package/docs/feature-requests/hidden-fields-subscribables.md +34 -0
  17. package/docs/feature-requests/subscribable-param-key-bug.md +38 -0
  18. package/docs/feature-requests/todo.md +146 -0
  19. package/docs/for_ai.md +641 -0
  20. package/docs/project-setup.md +432 -0
  21. package/examples/blog.ts +50 -0
  22. package/examples/invalid.ts +18 -0
  23. package/examples/venues.js +485 -0
  24. package/package.json +23 -60
  25. package/src/cli/codegen/client.ts +99 -0
  26. package/src/cli/codegen/manifest.ts +95 -0
  27. package/src/cli/codegen/pinia.ts +174 -0
  28. package/src/cli/codegen/realtime.ts +58 -0
  29. package/src/cli/codegen/sql.ts +698 -0
  30. package/src/cli/codegen/subscribable_sql.ts +547 -0
  31. package/src/cli/codegen/subscribable_store.ts +184 -0
  32. package/src/cli/codegen/types.ts +142 -0
  33. package/src/cli/compiler/analyzer.ts +52 -0
  34. package/src/cli/compiler/graph_rules.ts +251 -0
  35. package/src/cli/compiler/ir.ts +233 -0
  36. package/src/cli/compiler/loader.ts +132 -0
  37. package/src/cli/compiler/permissions.ts +227 -0
  38. package/src/cli/index.ts +164 -0
  39. package/src/client/index.ts +1 -0
  40. package/src/client/ws.ts +286 -0
  41. package/src/create/.env.example +8 -0
  42. package/src/create/README.md +101 -0
  43. package/src/create/compose.yml +14 -0
  44. package/src/create/domain.ts +153 -0
  45. package/src/create/package.json +24 -0
  46. package/src/create/server.ts +18 -0
  47. package/src/create/setup.sh +11 -0
  48. package/src/create/tsconfig.json +15 -0
  49. package/src/runtime/auth.ts +39 -0
  50. package/src/runtime/db.ts +33 -0
  51. package/src/runtime/errors.ts +51 -0
  52. package/src/runtime/index.ts +98 -0
  53. package/src/runtime/js_functions.ts +63 -0
  54. package/src/runtime/manifest_loader.ts +29 -0
  55. package/src/runtime/namespace.ts +483 -0
  56. package/src/runtime/server.ts +87 -0
  57. package/src/runtime/ws.ts +197 -0
  58. package/src/shared/ir.ts +197 -0
  59. package/tests/client.test.ts +38 -0
  60. package/tests/codegen.test.ts +71 -0
  61. package/tests/compiler.test.ts +45 -0
  62. package/tests/graph_rules.test.ts +173 -0
  63. package/tests/integration/db.test.ts +174 -0
  64. package/tests/integration/e2e.test.ts +65 -0
  65. package/tests/integration/features.test.ts +922 -0
  66. package/tests/integration/full_stack.test.ts +262 -0
  67. package/tests/integration/setup.ts +45 -0
  68. package/tests/ir.test.ts +32 -0
  69. package/tests/namespace.test.ts +395 -0
  70. package/tests/permissions.test.ts +55 -0
  71. package/tests/pinia.test.ts +48 -0
  72. package/tests/realtime.test.ts +22 -0
  73. package/tests/runtime.test.ts +80 -0
  74. package/tests/subscribable_gen.test.ts +72 -0
  75. package/tests/subscribable_reactivity.test.ts +258 -0
  76. package/tests/venues_gen.test.ts +25 -0
  77. package/tsconfig.json +20 -0
  78. package/tsconfig.tsbuildinfo +1 -0
  79. package/README.md +0 -90
  80. package/bin/cli.js +0 -727
  81. package/docs/compiler/ADVANCED_FILTERS.md +0 -183
  82. package/docs/compiler/CODING_STANDARDS.md +0 -415
  83. package/docs/compiler/COMPARISON.md +0 -673
  84. package/docs/compiler/QUICKSTART.md +0 -326
  85. package/docs/compiler/README.md +0 -134
  86. package/docs/examples/README.md +0 -38
  87. package/docs/examples/blog.sql +0 -160
  88. package/docs/examples/venue-detail-simple.sql +0 -8
  89. package/docs/examples/venue-detail-subscribable.sql +0 -45
  90. package/docs/for-ai/claude-guide.md +0 -1210
  91. package/docs/getting-started/quickstart.md +0 -125
  92. package/docs/getting-started/subscriptions-quick-start.md +0 -203
  93. package/docs/getting-started/tutorial.md +0 -1104
  94. package/docs/guides/atomic-updates.md +0 -299
  95. package/docs/guides/client-stores.md +0 -730
  96. package/docs/guides/composite-primary-keys.md +0 -158
  97. package/docs/guides/custom-functions.md +0 -362
  98. package/docs/guides/drop-semantics.md +0 -554
  99. package/docs/guides/field-defaults.md +0 -240
  100. package/docs/guides/interpreter-vs-compiler.md +0 -237
  101. package/docs/guides/many-to-many.md +0 -929
  102. package/docs/guides/subscriptions.md +0 -537
  103. package/docs/reference/api.md +0 -1373
  104. package/docs/reference/client.md +0 -224
  105. package/src/client/stores/index.js +0 -8
  106. package/src/client/stores/useAppStore.js +0 -285
  107. package/src/client/stores/useWsStore.js +0 -289
  108. package/src/client/ws.js +0 -762
  109. package/src/compiler/cli/compile-example.js +0 -33
  110. package/src/compiler/cli/compile-subscribable.js +0 -43
  111. package/src/compiler/cli/debug-compile.js +0 -44
  112. package/src/compiler/cli/debug-parse.js +0 -26
  113. package/src/compiler/cli/debug-path-parser.js +0 -18
  114. package/src/compiler/cli/debug-subscribable-parser.js +0 -21
  115. package/src/compiler/cli/index.js +0 -174
  116. package/src/compiler/codegen/auth-codegen.js +0 -153
  117. package/src/compiler/codegen/drop-semantics-codegen.js +0 -553
  118. package/src/compiler/codegen/graph-rules-codegen.js +0 -450
  119. package/src/compiler/codegen/notification-codegen.js +0 -232
  120. package/src/compiler/codegen/operation-codegen.js +0 -1382
  121. package/src/compiler/codegen/permission-codegen.js +0 -318
  122. package/src/compiler/codegen/subscribable-codegen.js +0 -827
  123. package/src/compiler/compiler.js +0 -371
  124. package/src/compiler/index.js +0 -11
  125. package/src/compiler/parser/entity-parser.js +0 -440
  126. package/src/compiler/parser/path-parser.js +0 -290
  127. package/src/compiler/parser/subscribable-parser.js +0 -244
  128. package/src/database/dzql-core.sql +0 -161
  129. package/src/database/migrations/001_schema.sql +0 -60
  130. package/src/database/migrations/002_functions.sql +0 -890
  131. package/src/database/migrations/003_operations.sql +0 -1135
  132. package/src/database/migrations/004_search.sql +0 -581
  133. package/src/database/migrations/005_entities.sql +0 -730
  134. package/src/database/migrations/006_auth.sql +0 -94
  135. package/src/database/migrations/007_events.sql +0 -133
  136. package/src/database/migrations/008_hello.sql +0 -18
  137. package/src/database/migrations/008a_meta.sql +0 -172
  138. package/src/database/migrations/009_subscriptions.sql +0 -240
  139. package/src/database/migrations/010_atomic_updates.sql +0 -157
  140. package/src/database/migrations/010_fix_m2m_events.sql +0 -94
  141. package/src/index.js +0 -40
  142. package/src/server/api.js +0 -9
  143. package/src/server/db.js +0 -442
  144. package/src/server/index.js +0 -317
  145. package/src/server/logger.js +0 -259
  146. package/src/server/mcp.js +0 -594
  147. package/src/server/meta-route.js +0 -251
  148. package/src/server/namespace.js +0 -292
  149. package/src/server/subscriptions.js +0 -351
  150. 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 $$;