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,730 +0,0 @@
1
- -- DZQL Entity Management - Version 3.0.0
2
- -- Entity registration, API functions creation, and graph rules execution
3
-
4
- -- ============================================================================
5
- -- DYNAMIC TRIGGER CREATION (for "execution": "trigger" actions)
6
- -- ============================================================================
7
-
8
- CREATE OR REPLACE FUNCTION dzql.create_event_trigger(
9
- p_table_name text,
10
- p_trigger_op text, -- 'on_create', 'on_update', 'on_delete'
11
- p_action_config jsonb
12
- ) RETURNS void
13
- LANGUAGE plpgsql AS $$
14
- DECLARE
15
- l_trigger_name text;
16
- l_function_name text;
17
- l_target_function text;
18
- l_params jsonb;
19
- l_param_key text;
20
- l_param_value text;
21
- l_param_list text[];
22
- l_record_ref text;
23
- l_sql text;
24
- BEGIN
25
- l_target_function := p_action_config->>'function';
26
- l_params := p_action_config->'params';
27
-
28
- -- Determine if we should use NEW or OLD record
29
- IF p_trigger_op = 'on_delete' THEN
30
- l_record_ref := 'OLD';
31
- ELSE
32
- l_record_ref := 'NEW';
33
- END IF;
34
-
35
- -- Construct the parameter list for the function call
36
- l_param_list := array[]::text[];
37
- IF l_params IS NOT NULL THEN
38
- FOR l_param_key, l_param_value IN SELECT * FROM jsonb_each_text(l_params)
39
- LOOP
40
- -- e.g., p_streak_id => NEW.streak_id
41
- l_param_list := l_param_list || format('%I => %s.%I', l_param_key, l_record_ref, right(l_param_value, -1));
42
- END LOOP;
43
- END IF;
44
-
45
- -- Generate unique names for the trigger and its function
46
- l_function_name := format('dzql_trigger_%s_%s_%s', p_table_name, p_trigger_op, l_target_function);
47
- l_trigger_name := format('dzql_managed_%s_%s', p_table_name, p_trigger_op);
48
-
49
- -- Create the trigger function
50
- l_sql := format(
51
- $SQL$
52
- CREATE OR REPLACE FUNCTION %I()
53
- RETURNS TRIGGER AS $trigger_func$
54
- BEGIN
55
- PERFORM %I(%s);
56
- RETURN NULL; -- Result is ignored for AFTER trigger
57
- END;
58
- $trigger_func$ LANGUAGE plpgsql;
59
- $SQL$,
60
- l_function_name,
61
- l_target_function,
62
- array_to_string(l_param_list, ', ')
63
- );
64
- EXECUTE l_sql;
65
-
66
- -- Create the trigger
67
- l_sql := format(
68
- $SQL$
69
- DROP TRIGGER IF EXISTS %I ON %I;
70
- CREATE TRIGGER %I
71
- AFTER %s ON %I
72
- FOR EACH ROW
73
- EXECUTE FUNCTION %I();
74
- $SQL$,
75
- l_trigger_name, p_table_name,
76
- l_trigger_name,
77
- CASE p_trigger_op WHEN 'on_create' THEN 'INSERT' WHEN 'on_update' THEN 'UPDATE' ELSE 'DELETE' END,
78
- p_table_name,
79
- l_function_name
80
- );
81
- EXECUTE l_sql;
82
-
83
- RAISE NOTICE 'DZQL: Created trigger % on % for % event', l_trigger_name, p_table_name, p_trigger_op;
84
- END;
85
- $$;
86
-
87
-
88
- -- ============================================================================
89
- -- GRAPH RULES EXECUTION ENGINE
90
- -- ============================================================================
91
-
92
- -- Execute graph insert action
93
- CREATE OR REPLACE FUNCTION dzql.execute_graph_insert(
94
- p_entity text,
95
- p_data jsonb,
96
- p_user_id int
97
- ) RETURNS void
98
- LANGUAGE plpgsql AS $$
99
- DECLARE
100
- l_cols text[];
101
- l_vals text[];
102
- l_col_name text;
103
- l_sql_stmt text;
104
- BEGIN
105
- -- Graph rules are trusted server-side operations, skip permission checks
106
-
107
- -- Build column and value lists
108
- FOR l_col_name IN SELECT * FROM jsonb_object_keys(p_data)
109
- LOOP
110
- l_cols := l_cols || l_col_name;
111
- l_vals := l_vals || quote_literal(p_data->>l_col_name);
112
- END LOOP;
113
-
114
- -- Build and execute INSERT statement
115
- l_sql_stmt := format('INSERT INTO %I (%s) VALUES (%s)',
116
- p_entity,
117
- array_to_string(l_cols, ', '),
118
- array_to_string(l_vals, ', ')
119
- );
120
-
121
- EXECUTE l_sql_stmt;
122
-
123
- -- Create event for graph rule action
124
- INSERT INTO dzql.events (
125
- table_name,
126
- op,
127
- pk,
128
- data,
129
- user_id,
130
- notify_users
131
- ) VALUES (
132
- p_entity,
133
- 'insert',
134
- jsonb_build_object('id', p_data->>'id'),
135
- p_data,
136
- p_user_id,
137
- dzql.resolve_notification_paths(p_entity, p_data)
138
- );
139
- END $$;
140
-
141
- -- Execute graph update action
142
- CREATE OR REPLACE FUNCTION dzql.execute_graph_update(
143
- p_entity text,
144
- p_match jsonb,
145
- p_data jsonb,
146
- p_user_id int
147
- ) RETURNS void
148
- LANGUAGE plpgsql AS $$
149
- DECLARE
150
- l_set_clauses text[];
151
- l_where_clauses text[];
152
- l_col_name text;
153
- l_sql_stmt text;
154
- BEGIN
155
- -- Check permissions before executing graph rule action
156
- -- Graph rules are trusted server-side operations, skip permission checks
157
-
158
- -- Build SET clauses
159
- FOR l_col_name IN SELECT * FROM jsonb_object_keys(p_data)
160
- LOOP
161
- l_set_clauses := l_set_clauses || format('%I = %L', l_col_name, p_data->>l_col_name);
162
- END LOOP;
163
-
164
- -- Build WHERE clauses
165
- FOR l_col_name IN SELECT * FROM jsonb_object_keys(p_match)
166
- LOOP
167
- l_where_clauses := l_where_clauses || format('%I = %L', l_col_name, p_match->>l_col_name);
168
- END LOOP;
169
-
170
- -- Build and execute UPDATE statement
171
- l_sql_stmt := format('UPDATE %I SET %s WHERE %s',
172
- p_entity,
173
- array_to_string(l_set_clauses, ', '),
174
- array_to_string(l_where_clauses, ' AND ')
175
- );
176
-
177
- EXECUTE l_sql_stmt;
178
-
179
- -- Create event for graph rule action
180
- INSERT INTO dzql.events (
181
- table_name,
182
- op,
183
- pk,
184
- data,
185
- user_id,
186
- notify_users
187
- ) VALUES (
188
- p_entity,
189
- 'update',
190
- p_match,
191
- p_data,
192
- p_user_id,
193
- '[]'::int[] -- Graph rule updates don't have notification paths
194
- );
195
- END $$;
196
-
197
- -- Execute graph delete action
198
- CREATE OR REPLACE FUNCTION dzql.execute_graph_delete(
199
- p_entity text,
200
- p_match jsonb,
201
- p_user_id int
202
- ) RETURNS void
203
- LANGUAGE plpgsql AS $$
204
- DECLARE
205
- l_where_clauses text[];
206
- l_col_name text;
207
- l_sql_stmt text;
208
- BEGIN
209
- -- Check permissions before executing graph rule action
210
- -- Graph rules are trusted server-side operations, skip permission checks
211
- -- Build WHERE clauses
212
- FOR l_col_name IN SELECT * FROM jsonb_object_keys(p_match)
213
- LOOP
214
- l_where_clauses := l_where_clauses || format('%I = %L', l_col_name, p_match->>l_col_name);
215
- END LOOP;
216
-
217
- -- Build and execute DELETE statement
218
- l_sql_stmt := format('DELETE FROM %I WHERE %s',
219
- p_entity,
220
- array_to_string(l_where_clauses, ' AND ')
221
- );
222
-
223
- EXECUTE l_sql_stmt;
224
-
225
- -- Create event for graph rule action
226
- INSERT INTO dzql.events (
227
- table_name,
228
- op,
229
- pk,
230
- data,
231
- user_id,
232
- notify_users
233
- ) VALUES (
234
- p_entity,
235
- 'delete',
236
- p_match,
237
- NULL,
238
- p_user_id,
239
- '[]'::int[] -- Graph rule deletes don't have notification paths
240
- );
241
- END $$;
242
-
243
- -- === Validate Action ===
244
- -- Calls a validation function and raises exception if it returns false
245
- CREATE OR REPLACE FUNCTION dzql.execute_graph_validate(
246
- p_function_name text,
247
- p_params jsonb,
248
- p_error_message text DEFAULT 'Validation failed'
249
- ) RETURNS void
250
- LANGUAGE plpgsql AS $$
251
- DECLARE
252
- l_result boolean;
253
- l_sql text;
254
- l_param_list text[];
255
- l_key text;
256
- l_value text;
257
- BEGIN
258
- -- Validate function name (prevent SQL injection)
259
- IF NOT p_function_name ~ '^[a-z_][a-z0-9_]*$' THEN
260
- RAISE EXCEPTION 'Invalid function name: %', p_function_name;
261
- END IF;
262
-
263
- -- Build parameter list
264
- l_param_list := array[]::text[];
265
- FOR l_key, l_value IN SELECT * FROM jsonb_each_text(p_params)
266
- LOOP
267
- l_param_list := l_param_list || (l_key || ' => ' || quote_literal(l_value));
268
- END LOOP;
269
-
270
- -- Build and execute function call
271
- IF array_length(l_param_list, 1) > 0 THEN
272
- l_sql := format('SELECT %I(%s)', p_function_name, array_to_string(l_param_list, ', '));
273
- ELSE
274
- l_sql := format('SELECT %I()', p_function_name);
275
- END IF;
276
-
277
- EXECUTE l_sql INTO l_result;
278
-
279
- -- Raise exception if validation failed
280
- IF NOT COALESCE(l_result, false) THEN
281
- RAISE EXCEPTION '%', p_error_message;
282
- END IF;
283
- END $$;
284
-
285
- -- === Execute Action ===
286
- -- Calls a custom function with parameters (fire-and-forget)
287
- CREATE OR REPLACE FUNCTION dzql.execute_graph_function(
288
- p_function_name text,
289
- p_params jsonb
290
- ) RETURNS jsonb
291
- LANGUAGE plpgsql AS $$
292
- DECLARE
293
- l_result jsonb;
294
- l_sql text;
295
- l_param_list text[];
296
- l_key text;
297
- l_value text;
298
- BEGIN
299
- -- Validate function name (prevent SQL injection)
300
- IF NOT p_function_name ~ '^[a-z_][a-z0-9_]*$' THEN
301
- RAISE EXCEPTION 'Invalid function name: %', p_function_name;
302
- END IF;
303
-
304
- -- Build parameter list
305
- l_param_list := array[]::text[];
306
- FOR l_key, l_value IN SELECT * FROM jsonb_each_text(p_params)
307
- LOOP
308
- l_param_list := l_param_list || (l_key || ' => ' || quote_literal(l_value));
309
- END LOOP;
310
-
311
- -- Build and execute function call
312
- IF array_length(l_param_list, 1) > 0 THEN
313
- l_sql := format('SELECT %I(%s)', p_function_name, array_to_string(l_param_list, ', '));
314
- ELSE
315
- l_sql := format('SELECT %I()', p_function_name);
316
- END IF;
317
-
318
- EXECUTE l_sql INTO l_result;
319
-
320
- RETURN COALESCE(l_result, '{}'::jsonb);
321
- EXCEPTION WHEN OTHERS THEN
322
- -- Log error but don't fail the transaction
323
- RAISE WARNING 'Graph rule function execution failed: % (function: %)', SQLERRM, p_function_name;
324
- RETURN jsonb_build_object('error', SQLERRM);
325
- END $$;
326
-
327
- -- Main graph rules execution engine
328
- CREATE OR REPLACE FUNCTION dzql.execute_graph_rules(
329
- p_table_name text,
330
- p_operation text, -- 'insert', 'update', 'delete'
331
- p_record_before jsonb,
332
- p_record_after jsonb,
333
- p_user_id int
334
- ) RETURNS jsonb
335
- LANGUAGE plpgsql AS $$
336
- DECLARE
337
- l_entity_config record;
338
- l_graph_rules jsonb;
339
- l_trigger_key text;
340
- l_trigger_rules jsonb;
341
- l_rule_name text;
342
- l_rule_config jsonb;
343
- l_action jsonb;
344
- l_action_type text;
345
- l_target_entity text;
346
- l_action_data jsonb;
347
- l_action_match jsonb;
348
- l_resolved_data jsonb;
349
- l_resolved_match jsonb;
350
- l_execution_log jsonb := '[]'::jsonb;
351
- l_condition text;
352
- l_condition_result boolean;
353
- l_function_name text;
354
- l_function_params jsonb;
355
- l_error_message text;
356
- l_function_result jsonb;
357
- BEGIN
358
- -- Get entity configuration
359
- SELECT * INTO l_entity_config FROM dzql.entities WHERE table_name = p_table_name;
360
-
361
- IF l_entity_config IS NULL THEN
362
- RETURN jsonb_build_object('status', 'entity_not_found');
363
- END IF;
364
-
365
- l_graph_rules := l_entity_config.graph_rules;
366
-
367
- -- Early exit if no graph rules
368
- IF l_graph_rules IS NULL OR l_graph_rules = '{}' THEN
369
- RETURN jsonb_build_object('status', 'no_rules');
370
- END IF;
371
-
372
- -- Map operation to trigger key
373
- l_trigger_key := CASE p_operation
374
- WHEN 'insert' THEN 'on_create'
375
- WHEN 'update' THEN 'on_update'
376
- WHEN 'delete' THEN 'on_delete'
377
- ELSE NULL
378
- END;
379
-
380
- IF l_trigger_key IS NULL THEN
381
- RETURN jsonb_build_object('status', 'invalid_operation', 'operation', p_operation);
382
- END IF;
383
-
384
- -- Get rules for this trigger
385
- l_trigger_rules := l_graph_rules->l_trigger_key;
386
-
387
- IF l_trigger_rules IS NULL OR l_trigger_rules = '{}' THEN
388
- RETURN jsonb_build_object('status', 'no_rules_for_trigger', 'trigger', l_trigger_key);
389
- END IF;
390
-
391
- -- Execute each rule
392
- FOR l_rule_name, l_rule_config IN SELECT * FROM jsonb_each(l_trigger_rules)
393
- LOOP
394
- -- Check condition if present
395
- l_condition := l_rule_config->>'condition';
396
- l_condition_result := true; -- Default to true if no condition
397
-
398
- IF l_condition IS NOT NULL THEN
399
- l_condition_result := dzql.evaluate_condition(l_condition, p_record_before, p_record_after, p_user_id);
400
- END IF;
401
-
402
- IF l_condition_result THEN
403
- -- Execute each action in the rule
404
- FOR l_action IN SELECT * FROM jsonb_array_elements(l_rule_config->'actions')
405
- LOOP
406
- -- If action is handled by a native trigger, skip it in the immediate executor
407
- IF l_action->>'execution' = 'trigger' THEN
408
- CONTINUE;
409
- END IF;
410
-
411
- l_action_type := l_action->>'type';
412
-
413
- -- Execute the action based on type
414
- BEGIN
415
- CASE l_action_type
416
- -- Existing action types
417
- WHEN 'create' THEN
418
- l_target_entity := l_action->>'entity';
419
- l_action_data := l_action->'data';
420
- l_resolved_data := dzql.resolve_graph_data(l_action_data, p_record_before, p_record_after, p_user_id);
421
- PERFORM dzql.execute_graph_insert(l_target_entity, l_resolved_data, p_user_id);
422
-
423
- WHEN 'update' THEN
424
- l_target_entity := l_action->>'entity';
425
- l_action_data := l_action->'data';
426
- l_action_match := l_action->'match';
427
- l_resolved_data := dzql.resolve_graph_data(l_action_data, p_record_before, p_record_after, p_user_id);
428
- l_resolved_match := dzql.resolve_graph_data(l_action_match, p_record_before, p_record_after, p_user_id);
429
- PERFORM dzql.execute_graph_update(l_target_entity, l_resolved_match, l_resolved_data, p_user_id);
430
-
431
- WHEN 'delete' THEN
432
- l_target_entity := l_action->>'entity';
433
- l_action_match := l_action->'match';
434
- l_resolved_match := dzql.resolve_graph_data(l_action_match, p_record_before, p_record_after, p_user_id);
435
- PERFORM dzql.execute_graph_delete(l_target_entity, l_resolved_match, p_user_id);
436
-
437
- -- NEW: Validation action
438
- WHEN 'validate' THEN
439
- l_function_name := l_action->>'function';
440
- l_function_params := l_action->'params';
441
- l_error_message := COALESCE(l_action->>'error_message', 'Validation failed');
442
- l_resolved_data := dzql.resolve_graph_data(l_function_params, p_record_before, p_record_after, p_user_id);
443
- PERFORM dzql.execute_graph_validate(l_function_name, l_resolved_data, l_error_message);
444
-
445
- -- NEW: Execute function action
446
- WHEN 'execute' THEN
447
- l_function_name := l_action->>'function';
448
- l_function_params := l_action->'params';
449
- l_resolved_data := dzql.resolve_graph_data(l_function_params, p_record_before, p_record_after, p_user_id);
450
- l_function_result := dzql.execute_graph_function(l_function_name, l_resolved_data);
451
-
452
- ELSE
453
- RAISE WARNING 'Unknown graph rule action type: %', l_action_type;
454
- END CASE;
455
-
456
- -- Log successful execution
457
- l_execution_log := l_execution_log || jsonb_build_object(
458
- 'rule', l_rule_name,
459
- 'action', l_action_type,
460
- 'status', 'success'
461
- );
462
-
463
- EXCEPTION WHEN OTHERS THEN
464
- -- Log error and re-raise for validate actions, otherwise just log
465
- l_execution_log := l_execution_log || jsonb_build_object(
466
- 'rule', l_rule_name,
467
- 'action', l_action_type,
468
- 'status', 'error',
469
- 'error', SQLERRM
470
- );
471
-
472
- -- Re-raise exceptions from validate actions to prevent operation
473
- IF l_action_type = 'validate' THEN
474
- RAISE;
475
- END IF;
476
- END;
477
- END LOOP;
478
- END IF;
479
- END LOOP;
480
-
481
- RETURN jsonb_build_object(
482
- 'status', 'success',
483
- 'trigger', l_trigger_key,
484
- 'executed_actions', l_execution_log
485
- );
486
- END $$;
487
-
488
- -- ============================================================================
489
- -- API FUNCTION CREATION
490
- -- ============================================================================
491
-
492
- -- Create API functions for an entity
493
- CREATE OR REPLACE FUNCTION dzql.create_entity_functions(p_table_name text)
494
- RETURNS void
495
- LANGUAGE plpgsql AS $$
496
- DECLARE
497
- l_get_fn_name text;
498
- l_save_fn_name text;
499
- l_delete_fn_name text;
500
- l_lookup_fn_name text;
501
- l_search_fn_name text;
502
- BEGIN
503
- -- Generate function names
504
- l_get_fn_name := 'get_' || p_table_name;
505
- l_save_fn_name := 'save_' || p_table_name;
506
- l_delete_fn_name := 'delete_' || p_table_name;
507
- l_lookup_fn_name := 'lookup_' || p_table_name;
508
- l_search_fn_name := 'search_' || p_table_name;
509
-
510
- -- Create GET function
511
- EXECUTE format('
512
- CREATE OR REPLACE FUNCTION dzql.%I(p_args jsonb, p_user_id int)
513
- RETURNS jsonb
514
- LANGUAGE sql
515
- AS $func$
516
- SELECT dzql.generic_get(%L, p_args, p_user_id);
517
- $func$;
518
- ', l_get_fn_name, p_table_name);
519
-
520
- -- Create SAVE function
521
- EXECUTE format('
522
- CREATE OR REPLACE FUNCTION dzql.%I(p_args jsonb, p_user_id int)
523
- RETURNS jsonb
524
- LANGUAGE sql
525
- AS $func$
526
- SELECT dzql.generic_save(%L, p_args, p_user_id);
527
- $func$;
528
- ', l_save_fn_name, p_table_name);
529
-
530
- -- Create DELETE function
531
- EXECUTE format('
532
- CREATE OR REPLACE FUNCTION dzql.%I(p_args jsonb, p_user_id int)
533
- RETURNS jsonb
534
- LANGUAGE sql
535
- AS $func$
536
- SELECT dzql.generic_delete(%L, p_args, p_user_id);
537
- $func$;
538
- ', l_delete_fn_name, p_table_name);
539
-
540
- -- Create LOOKUP function
541
- EXECUTE format('
542
- CREATE OR REPLACE FUNCTION dzql.%I(p_args jsonb, p_user_id int)
543
- RETURNS jsonb
544
- LANGUAGE sql
545
- AS $func$
546
- SELECT dzql.generic_lookup(%L, p_args, p_user_id);
547
- $func$;
548
- ', l_lookup_fn_name, p_table_name);
549
-
550
- -- Create SEARCH function
551
- EXECUTE format('
552
- CREATE OR REPLACE FUNCTION dzql.%I(p_args jsonb, p_user_id int)
553
- RETURNS jsonb
554
- LANGUAGE sql
555
- AS $func$
556
- SELECT dzql.generic_search(%L, p_args, p_user_id);
557
- $func$;
558
- ', l_search_fn_name, p_table_name);
559
- END $$;
560
-
561
- -- ============================================================================
562
- -- ENTITY REGISTRATION
563
- -- ============================================================================
564
-
565
- -- Register entity function with full graph rules support
566
- CREATE OR REPLACE FUNCTION dzql.register_entity(
567
- p_table_name text,
568
- p_label_field text,
569
- p_searchable_fields text[],
570
- p_fk_includes jsonb DEFAULT '{}',
571
- p_soft_delete boolean DEFAULT false,
572
- p_temporal_fields jsonb DEFAULT '{}',
573
- p_notification_paths jsonb DEFAULT '{}',
574
- p_permission_paths jsonb DEFAULT '{}',
575
- p_graph_rules jsonb DEFAULT '{}',
576
- p_field_defaults jsonb DEFAULT '{}'
577
- ) RETURNS void
578
- LANGUAGE plpgsql AS $$
579
- DECLARE
580
- l_trigger_op text;
581
- l_rule_name text;
582
- l_rule_config jsonb;
583
- l_action jsonb;
584
- l_many_to_many jsonb;
585
- BEGIN
586
- -- Validate permission paths if provided
587
- IF p_permission_paths IS NOT NULL AND p_permission_paths != '{}' THEN
588
- IF NOT dzql.validate_permission_paths(p_table_name, p_permission_paths) THEN
589
- RAISE EXCEPTION 'Invalid permission paths for entity %', p_table_name;
590
- END IF;
591
- END IF;
592
-
593
- -- Validate graph rules if provided
594
- IF p_graph_rules IS NOT NULL AND p_graph_rules != '{}' THEN
595
- IF NOT dzql.validate_graph_rules(p_graph_rules) THEN
596
- RAISE EXCEPTION 'Invalid graph rules for entity %', p_table_name;
597
- END IF;
598
- END IF;
599
-
600
- -- Extract many_to_many from graph_rules if present
601
- l_many_to_many := COALESCE(p_graph_rules->'many_to_many', '{}'::jsonb);
602
-
603
- -- Insert or update entity configuration
604
- INSERT INTO dzql.entities
605
- (table_name, label_field, searchable_fields, fk_includes, soft_delete, temporal_fields, notification_paths, permission_paths, graph_rules, field_defaults, many_to_many)
606
- VALUES
607
- (p_table_name, p_label_field, p_searchable_fields, p_fk_includes, p_soft_delete, p_temporal_fields, p_notification_paths, p_permission_paths, p_graph_rules, p_field_defaults, l_many_to_many)
608
- ON CONFLICT (table_name) DO UPDATE SET
609
- label_field = EXCLUDED.label_field,
610
- searchable_fields = EXCLUDED.searchable_fields,
611
- fk_includes = EXCLUDED.fk_includes,
612
- soft_delete = EXCLUDED.soft_delete,
613
- temporal_fields = EXCLUDED.temporal_fields,
614
- notification_paths = EXCLUDED.notification_paths,
615
- permission_paths = EXCLUDED.permission_paths,
616
- graph_rules = EXCLUDED.graph_rules,
617
- field_defaults = EXCLUDED.field_defaults,
618
- many_to_many = EXCLUDED.many_to_many;
619
-
620
- -- Create API functions for this entity
621
- PERFORM dzql.create_entity_functions(p_table_name);
622
-
623
- -- Create managed triggers for any 'execution: trigger' actions
624
- IF p_graph_rules IS NOT NULL AND p_graph_rules != '{}' THEN
625
- FOREACH l_trigger_op IN ARRAY ARRAY['on_create', 'on_update', 'on_delete']
626
- LOOP
627
- IF p_graph_rules ? l_trigger_op THEN
628
- FOR l_rule_name, l_rule_config IN SELECT * FROM jsonb_each(p_graph_rules->l_trigger_op)
629
- LOOP
630
- FOR l_action IN SELECT * FROM jsonb_array_elements(l_rule_config->'actions')
631
- LOOP
632
- IF l_action->>'execution' = 'trigger' THEN
633
- PERFORM dzql.create_event_trigger(p_table_name, l_trigger_op, l_action);
634
- END IF;
635
- END LOOP;
636
- END LOOP;
637
- END IF;
638
- END LOOP;
639
- END IF;
640
-
641
- -- Log successful registration
642
- RAISE NOTICE 'DZQL: Entity % registered successfully with graph rules support', p_table_name;
643
- END $$;
644
-
645
- -- ============================================================================
646
- -- ENTITY UTILITIES
647
- -- ============================================================================
648
-
649
- -- Unregister an entity (removes configuration and API functions)
650
- CREATE OR REPLACE FUNCTION dzql.unregister_entity(p_table_name text)
651
- RETURNS void
652
- LANGUAGE plpgsql AS $$
653
- DECLARE
654
- l_fn_names text[] := ARRAY['get_', 'save_', 'delete_', 'lookup_', 'search_'];
655
- l_fn_name text;
656
- BEGIN
657
- -- Remove entity configuration
658
- DELETE FROM dzql.entities WHERE table_name = p_table_name;
659
-
660
- -- Drop API functions
661
- FOREACH l_fn_name IN ARRAY l_fn_names
662
- LOOP
663
- EXECUTE format('DROP FUNCTION IF EXISTS dzql.%I(jsonb, int)', l_fn_name || p_table_name);
664
- END LOOP;
665
-
666
- RAISE NOTICE 'DZQL: Entity % unregistered successfully', p_table_name;
667
- END $$;
668
-
669
- -- List all registered entities
670
- CREATE OR REPLACE FUNCTION dzql.list_entities()
671
- RETURNS TABLE(
672
- table_name text,
673
- label_field text,
674
- searchable_fields text[],
675
- has_fk_includes boolean,
676
- soft_delete boolean,
677
- has_temporal_fields boolean,
678
- has_notification_paths boolean,
679
- has_permission_paths boolean,
680
- has_graph_rules boolean
681
- )
682
- LANGUAGE sql AS $$
683
- SELECT
684
- e.table_name,
685
- e.label_field,
686
- e.searchable_fields,
687
- (e.fk_includes IS NOT NULL AND e.fk_includes != '{}') as has_fk_includes,
688
- e.soft_delete,
689
- (e.temporal_fields IS NOT NULL AND e.temporal_fields != '{}') as has_temporal_fields,
690
- (e.notification_paths IS NOT NULL AND e.notification_paths != '{}') as has_notification_paths,
691
- (e.permission_paths IS NOT NULL AND e.permission_paths != '{}') as has_permission_paths,
692
- (e.graph_rules IS NOT NULL AND e.graph_rules != '{}') as has_graph_rules
693
- FROM dzql.entities e
694
- ORDER BY e.table_name;
695
- $$;
696
-
697
- -- Get detailed entity configuration
698
- CREATE OR REPLACE FUNCTION dzql.get_entity_config(p_table_name text)
699
- RETURNS jsonb
700
- LANGUAGE sql AS $$
701
- SELECT to_jsonb(e.*)
702
- FROM dzql.entities e
703
- WHERE e.table_name = p_table_name;
704
- $$;
705
-
706
- -- Update entity graph rules only
707
- CREATE OR REPLACE FUNCTION dzql.update_entity_graph_rules(
708
- p_table_name text,
709
- p_graph_rules jsonb
710
- ) RETURNS void
711
- LANGUAGE plpgsql AS $$
712
- BEGIN
713
- -- Validate graph rules
714
- IF p_graph_rules IS NOT NULL AND p_graph_rules != '{}' THEN
715
- IF NOT dzql.validate_graph_rules(p_graph_rules) THEN
716
- RAISE EXCEPTION 'Invalid graph rules for entity %', p_table_name;
717
- END IF;
718
- END IF;
719
-
720
- -- Update only graph rules
721
- UPDATE dzql.entities
722
- SET graph_rules = p_graph_rules
723
- WHERE table_name = p_table_name;
724
-
725
- IF NOT FOUND THEN
726
- RAISE EXCEPTION 'Entity % not found', p_table_name;
727
- END IF;
728
-
729
- RAISE NOTICE 'DZQL: Graph rules updated for entity %', p_table_name;
730
- END $$;