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,673 +0,0 @@
1
- # Runtime vs Compiled DZQL - Side-by-Side Comparison
2
-
3
- This document shows the architectural differences between the current runtime-interpreted DZQL and the new compiled approach.
4
-
5
- ## Entity Registration
6
-
7
- ### Runtime (Current)
8
-
9
- **Entity definition is stored as JSON in database:**
10
-
11
- ```sql
12
- -- Stored in dzql.entities table
13
- INSERT INTO dzql.entities (
14
- table_name,
15
- label_field,
16
- searchable_fields,
17
- fk_includes,
18
- permission_paths,
19
- notification_paths,
20
- graph_rules
21
- ) VALUES (
22
- 'venues',
23
- 'name',
24
- ARRAY['name', 'address'],
25
- '{"org": "organisations"}'::jsonb,
26
- '{"update": ["@org_id->acts_for[org_id=$]{active}.user_id"]}'::jsonb,
27
- '{"ownership": ["@org_id->acts_for[org_id=$]{active}.user_id"]}'::jsonb,
28
- '{"on_create": {...}}'::jsonb
29
- );
30
- ```
31
-
32
- **Every operation parses this JSON at runtime:**
33
-
34
- ```sql
35
- SELECT dzql.generic_exec('save', 'venues', '{"name": "MSG"}'::jsonb, 42);
36
-
37
- dzql.generic_save('venues', ...)
38
-
39
- 1. Fetch entity config from dzql.entities (JSONB parse)
40
- 2. Parse permission_paths JSON
41
- 3. Evaluate paths dynamically
42
- 4. Parse graph_rules JSON
43
- 5. Execute rules via dynamic SQL
44
- 6. Parse notification_paths JSON
45
- 7. Resolve paths dynamically
46
- 8. Finally execute the save
47
- ```
48
-
49
- **Cost per request:**
50
- - 1 table lookup (`dzql.entities`)
51
- - 3-5 JSONB parse operations
52
- - Dynamic SQL generation
53
- - Path resolution interpretation
54
- - Graph rule interpretation
55
-
56
- ---
57
-
58
- ### Compiled (New)
59
-
60
- **Entity definition compiles to native functions:**
61
-
62
- ```sql
63
- -- Permission check (pre-compiled)
64
- CREATE OR REPLACE FUNCTION can_update_venues(p_user_id INT, p_record JSONB)
65
- RETURNS BOOLEAN AS $$
66
- BEGIN
67
- RETURN EXISTS (
68
- SELECT 1 FROM acts_for
69
- WHERE org_id = (p_record->>'org_id')::int
70
- AND user_id = p_user_id
71
- AND valid_to IS NULL -- {active}
72
- );
73
- END;
74
- $$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
75
-
76
- -- Save operation (pre-compiled)
77
- CREATE OR REPLACE FUNCTION save_venues(p_data JSONB, p_user_id INT)
78
- RETURNS JSONB AS $$
79
- DECLARE
80
- v_result venues%ROWTYPE;
81
- BEGIN
82
- -- Check permission (direct function call)
83
- IF NOT can_update_venues(p_user_id, p_data) THEN
84
- RAISE EXCEPTION 'Permission denied';
85
- END IF;
86
-
87
- -- Perform save
88
- INSERT INTO venues (...) VALUES (...)
89
- ON CONFLICT (id) DO UPDATE SET ...
90
- RETURNING * INTO v_result;
91
-
92
- -- Execute graph rules (pre-compiled)
93
- -- ...
94
-
95
- -- Resolve notifications (pre-compiled)
96
- -- ...
97
-
98
- RETURN to_jsonb(v_result);
99
- END;
100
- $$ LANGUAGE plpgsql SECURITY DEFINER;
101
- ```
102
-
103
- **Direct function call:**
104
-
105
- ```sql
106
- SELECT save_venues('{"name": "MSG"}'::jsonb, 42);
107
-
108
- save_venues(...)
109
-
110
- 1. can_update_venues() - optimized SQL
111
- 2. INSERT/UPDATE - direct
112
- 3. Graph rules - pre-compiled
113
- 4. Notifications - pre-compiled
114
- 5. Done
115
- ```
116
-
117
- **Cost per request:**
118
- - 0 JSON config lookups
119
- - 0 dynamic SQL generation
120
- - 0 path interpretation
121
- - Just the actual work
122
-
123
- ---
124
-
125
- ## Performance Comparison
126
-
127
- ### GET Operation
128
-
129
- #### Runtime (Current)
130
-
131
- ```sql
132
- SELECT dzql.generic_exec('get', 'venues', '{"id": 1}'::jsonb, 42);
133
- ```
134
-
135
- **Execution path:**
136
- 1. Call `dzql.generic_exec()`
137
- 2. Route to `dzql.generic_get()`
138
- 3. `SELECT * FROM dzql.entities WHERE table_name = 'venues'` ← **lookup**
139
- 4. Parse `fk_includes` JSONB ← **parse**
140
- 5. Parse `permission_paths` JSONB ← **parse**
141
- 6. Build permission query dynamically ← **generate SQL**
142
- 7. Check permission via `dzql.check_permission()` ← **interpret path**
143
- 8. Fetch record: `SELECT * FROM venues WHERE id = 1`
144
- 9. Loop through `fk_includes` JSONB ← **interpret**
145
- 10. For each FK, call `dzql.resolve_direct_fk()` ← **dynamic SQL**
146
- 11. Return result
147
-
148
- **Total:** ~11 steps, 3 JSONB parses, 2 dynamic SQL generations
149
-
150
- ---
151
-
152
- #### Compiled (New)
153
-
154
- ```sql
155
- SELECT get_venues(1, 42);
156
- ```
157
-
158
- **Execution path:**
159
- 1. Call `get_venues()`
160
- 2. Check permission via `can_view_venues()` ← **pre-compiled**
161
- 3. Fetch record: `SELECT * FROM venues WHERE id = 1`
162
- 4. Expand FK: `SELECT * FROM organisations WHERE id = v.org_id` ← **pre-compiled**
163
- 5. Expand children: `SELECT * FROM sites WHERE venue_id = 1` ← **pre-compiled**
164
- 6. Return result
165
-
166
- **Total:** ~6 steps, 0 JSONB parses, 0 dynamic SQL
167
-
168
- **Speedup:** ~2-3x faster, more consistent query plans
169
-
170
- ---
171
-
172
- ## Permission Checking
173
-
174
- ### Runtime (Current)
175
-
176
- ```sql
177
- -- Permission path stored as JSON string
178
- permission_paths: {
179
- "update": ["@org_id->acts_for[org_id=$]{active}.user_id"]
180
- }
181
- ```
182
-
183
- **At runtime:**
184
-
185
- ```plpgsql
186
- -- In dzql.check_permission()
187
- l_paths := entity_config.permission_paths->operation; -- JSONB parse
188
-
189
- FOR l_path IN SELECT * FROM jsonb_array_elements_text(l_paths)
190
- LOOP
191
- -- Parse path string: "@org_id->acts_for[org_id=$]{active}.user_id"
192
- l_result := dzql.resolve_path(table_name, record, l_path);
193
- -- Dynamically build and execute SQL:
194
- -- SELECT user_id FROM acts_for WHERE org_id = ... AND valid_to IS NULL
195
-
196
- IF l_result @> to_jsonb(user_id) THEN
197
- RETURN true;
198
- END IF;
199
- END LOOP;
200
- ```
201
-
202
- **Problems:**
203
- - Path parsed every time
204
- - SQL built dynamically
205
- - Hard to optimize
206
- - No query plan caching
207
-
208
- ---
209
-
210
- ### Compiled (New)
211
-
212
- ```sql
213
- CREATE OR REPLACE FUNCTION can_update_venues(p_user_id INT, p_record JSONB)
214
- RETURNS BOOLEAN AS $$
215
- BEGIN
216
- RETURN EXISTS (
217
- SELECT 1 FROM acts_for
218
- WHERE org_id = (p_record->>'org_id')::int
219
- AND user_id = p_user_id
220
- AND valid_to IS NULL
221
- );
222
- END;
223
- $$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
224
- ```
225
-
226
- **Benefits:**
227
- - Path pre-compiled to SQL
228
- - PostgreSQL can plan and cache it
229
- - Can use indexes effectively
230
- - Easy to debug with EXPLAIN
231
- - Consistent performance
232
-
233
- ---
234
-
235
- ## Notification Resolution
236
-
237
- ### Runtime (Current)
238
-
239
- ```sql
240
- -- Notification path stored as JSON
241
- notification_paths: {
242
- "ownership": ["@org_id->acts_for[org_id=$]{active}.user_id"]
243
- }
244
- ```
245
-
246
- **At runtime:**
247
-
248
- ```plpgsql
249
- -- In dzql.resolve_notification_paths()
250
- l_paths := entity_config.notification_paths; -- JSONB parse
251
-
252
- FOR l_path_name, l_path IN SELECT * FROM jsonb_each(l_paths)
253
- LOOP
254
- FOR l_path_str IN SELECT * FROM jsonb_array_elements_text(l_path)
255
- LOOP
256
- -- Parse and resolve each path dynamically
257
- l_users := l_users || dzql.resolve_notification_path(table_name, record, l_path_str);
258
- END LOOP;
259
- END LOOP;
260
-
261
- -- Insert into events table with dynamic user list
262
- INSERT INTO dzql.events (notify_users) VALUES (l_users);
263
- ```
264
-
265
- **Problems:**
266
- - Multiple JSONB parses
267
- - Multiple path resolutions
268
- - Hard to optimize
269
- - Can't use indexes well
270
-
271
- ---
272
-
273
- ### Compiled (New)
274
-
275
- ```sql
276
- CREATE OR REPLACE FUNCTION resolve_notification_paths_venues(p_record JSONB)
277
- RETURNS INT[] AS $$
278
- BEGIN
279
- RETURN ARRAY(
280
- SELECT DISTINCT user_id
281
- FROM acts_for
282
- WHERE org_id = (p_record->>'org_id')::int
283
- AND valid_to IS NULL
284
- );
285
- END;
286
- $$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
287
- ```
288
-
289
- **Called in save_venues:**
290
-
291
- ```plpgsql
292
- -- Resolve notifications (direct function call)
293
- v_notify_users := resolve_notification_paths_venues(to_jsonb(v_result));
294
-
295
- -- Insert event
296
- INSERT INTO dzql.events (notify_users) VALUES (v_notify_users);
297
- ```
298
-
299
- **Benefits:**
300
- - Single function call
301
- - Pre-optimized SQL
302
- - Can use indexes
303
- - Consistent query plan
304
-
305
- ---
306
-
307
- ## Graph Rules Execution
308
-
309
- ### Runtime (Current)
310
-
311
- ```sql
312
- -- Graph rules stored as nested JSON
313
- graph_rules: {
314
- "on_create": {
315
- "establish_ownership": {
316
- "actions": [
317
- {
318
- "type": "create",
319
- "entity": "acts_for",
320
- "data": {
321
- "user_id": "@user_id",
322
- "org_id": "@id"
323
- }
324
- }
325
- ]
326
- }
327
- }
328
- }
329
- ```
330
-
331
- **At runtime:**
332
-
333
- ```plpgsql
334
- -- In dzql.execute_graph_rules()
335
- l_rules := entity_config.graph_rules->trigger; -- JSONB parse
336
-
337
- FOR l_rule_name, l_rule_config IN SELECT * FROM jsonb_each(l_rules)
338
- LOOP
339
- FOR l_action IN SELECT * FROM jsonb_array_elements(l_rule_config->'actions')
340
- LOOP
341
- l_action_type := l_action->>'type'; -- JSONB parse
342
-
343
- CASE l_action_type
344
- WHEN 'create' THEN
345
- l_entity := l_action->>'entity'; -- JSONB parse
346
- l_data := dzql.resolve_graph_data(l_action->'data', record, user_id); -- JSONB parse + resolution
347
- PERFORM dzql.execute_graph_insert(l_entity, l_data, user_id); -- Dynamic INSERT
348
- WHEN 'update' THEN
349
- -- Similar dynamic execution
350
- END CASE;
351
- END LOOP;
352
- END LOOP;
353
- ```
354
-
355
- **Problems:**
356
- - Multiple levels of JSONB parsing
357
- - Dynamic SQL for every action
358
- - Hard to debug
359
- - Performance unpredictable
360
-
361
- ---
362
-
363
- ### Compiled (New - Planned)
364
-
365
- ```sql
366
- -- Generated for graph rule
367
- CREATE OR REPLACE FUNCTION graph_venues_on_create(
368
- p_record JSONB,
369
- p_user_id INT
370
- ) RETURNS VOID AS $$
371
- BEGIN
372
- -- establish_ownership action (pre-compiled)
373
- INSERT INTO acts_for (user_id, org_id, valid_from)
374
- VALUES (
375
- p_user_id,
376
- (p_record->>'id')::int,
377
- CURRENT_DATE
378
- );
379
- END;
380
- $$ LANGUAGE plpgsql SECURITY DEFINER;
381
- ```
382
-
383
- **Called in save_venues:**
384
-
385
- ```plpgsql
386
- -- Execute graph rules (direct function call)
387
- IF v_is_insert THEN
388
- PERFORM graph_venues_on_create(to_jsonb(v_result), p_user_id);
389
- END IF;
390
- ```
391
-
392
- **Benefits:**
393
- - No JSONB parsing
394
- - Direct SQL execution
395
- - Atomic with transaction
396
- - Easy to debug and optimize
397
-
398
- ---
399
-
400
- ## FK Expansion
401
-
402
- ### Runtime (Current)
403
-
404
- ```sql
405
- -- FK includes stored as JSON
406
- fk_includes: {
407
- "org": "organisations",
408
- "sites": "sites"
409
- }
410
- ```
411
-
412
- **At runtime:**
413
-
414
- ```plpgsql
415
- -- In dzql.generic_get()
416
- l_fk_includes := entity_config.fk_includes; -- JSONB parse
417
-
418
- FOR l_key, l_value IN SELECT * FROM jsonb_each_text(l_fk_includes)
419
- LOOP
420
- -- Determine if direct or reverse FK
421
- IF l_key = l_value THEN
422
- -- Reverse FK (child array)
423
- l_fk_result := dzql.resolve_reverse_fk(record, l_key, l_value || '.' || entity || '_id');
424
- ELSE
425
- -- Direct FK
426
- l_fk_result := dzql.resolve_direct_fk(record, l_key, l_value);
427
- END IF;
428
-
429
- -- Merge into result
430
- l_result := l_result || jsonb_build_object(l_key, l_fk_result);
431
- END LOOP;
432
- ```
433
-
434
- **Problems:**
435
- - JSONB parsing
436
- - Loop overhead
437
- - Dynamic SQL in resolve functions
438
- - Multiple separate queries
439
-
440
- ---
441
-
442
- ### Compiled (New)
443
-
444
- ```sql
445
- -- In get_venues()
446
- BEGIN
447
- -- Fetch record
448
- SELECT * INTO v_record FROM venues WHERE id = p_id;
449
- v_result := to_jsonb(v_record);
450
-
451
- -- Expand org (direct FK - pre-compiled)
452
- IF v_record.org_id IS NOT NULL THEN
453
- v_result := v_result || jsonb_build_object(
454
- 'org',
455
- (SELECT to_jsonb(t.*) FROM organisations t WHERE t.id = v_record.org_id)
456
- );
457
- END IF;
458
-
459
- -- Expand sites (reverse FK - pre-compiled)
460
- v_result := v_result || jsonb_build_object(
461
- 'sites',
462
- (SELECT COALESCE(jsonb_agg(to_jsonb(t.*)), '[]'::jsonb)
463
- FROM sites t
464
- WHERE t.venue_id = v_record.id)
465
- );
466
-
467
- RETURN v_result;
468
- END;
469
- ```
470
-
471
- **Benefits:**
472
- - No loops or JSONB parsing
473
- - Direct SQL queries
474
- - PostgreSQL can optimize joins
475
- - Predictable performance
476
-
477
- ---
478
-
479
- ## Debugging & Observability
480
-
481
- ### Runtime (Current)
482
-
483
- **Query Analysis:**
484
- ```sql
485
- EXPLAIN ANALYZE SELECT dzql.generic_exec('save', 'venues', '{...}'::jsonb, 42);
486
- ```
487
-
488
- **Result:**
489
- ```
490
- Function Scan on generic_exec
491
- -> Function Scan on generic_save
492
- -> Seq Scan on dzql.entities (filter: table_name = 'venues')
493
- -> Result (JSONB operations)
494
- -> Function Scan on check_permission
495
- -> Result (dynamic SQL from path resolution)
496
- -> Result (dynamic INSERT/UPDATE)
497
- -> Function Scan on execute_graph_rules
498
- -> Result (JSONB parsing and dynamic SQL)
499
- -> Function Scan on resolve_notification_paths
500
- -> Result (dynamic SQL from path resolution)
501
- ```
502
-
503
- **Problems:**
504
- - Hard to understand
505
- - Generic query plans
506
- - Can't see actual operations
507
- - Hard to optimize
508
-
509
- ---
510
-
511
- ### Compiled (New)
512
-
513
- **Query Analysis:**
514
- ```sql
515
- EXPLAIN ANALYZE SELECT save_venues('{...}'::jsonb, 42);
516
- ```
517
-
518
- **Result:**
519
- ```
520
- Function Scan on save_venues
521
- -> Function Scan on can_update_venues
522
- -> Index Scan on acts_for (org_id, user_id)
523
- -> Index Scan on venues (id)
524
- -> Result (INSERT/UPDATE)
525
- -> Function Scan on graph_venues_on_create
526
- -> Insert on acts_for
527
- -> Function Scan on resolve_notification_paths_venues
528
- -> Index Scan on acts_for (org_id)
529
- ```
530
-
531
- **Benefits:**
532
- - Clear execution path
533
- - Specific query plans
534
- - Can see index usage
535
- - Easy to optimize
536
-
537
- **Standard PostgreSQL Tools Work:**
538
- ```sql
539
- -- View slow queries
540
- SELECT * FROM pg_stat_statements
541
- WHERE query LIKE '%save_venues%'
542
- ORDER BY total_time DESC;
543
-
544
- -- Analyze specific function
545
- EXPLAIN (ANALYZE, BUFFERS) SELECT save_venues('{...}'::jsonb, 42);
546
-
547
- -- View function source
548
- \sf save_venues
549
- ```
550
-
551
- ---
552
-
553
- ## Development Workflow
554
-
555
- ### Runtime (Current)
556
-
557
- 1. **Define entity:**
558
- ```javascript
559
- registerEntity('venues', {
560
- labelField: 'name',
561
- permissions: { ... },
562
- graphRules: { ... }
563
- });
564
- ```
565
-
566
- 2. **Server restarts** → Entity registered in database
567
-
568
- 3. **Immediate use:**
569
- ```javascript
570
- await db.api.save.venues({ name: 'MSG' }, userId);
571
- ```
572
-
573
- 4. **Debug issues:**
574
- - Enable query logging
575
- - Add console.log in generic_exec
576
- - Inspect dzql.entities table
577
- - Hope the path resolution works
578
-
579
- **Pros:**
580
- - ✅ Fast iteration
581
- - ✅ No build step
582
- - ✅ Dynamic changes
583
-
584
- **Cons:**
585
- - ❌ Hard to debug
586
- - ❌ Performance unpredictable
587
- - ❌ No type safety
588
- - ❌ Opaque behavior
589
-
590
- ---
591
-
592
- ### Compiled (New)
593
-
594
- 1. **Define entity:**
595
- ```sql
596
- select dzql.register_entity('venues', ...);
597
- ```
598
-
599
- 2. **Compile:**
600
- ```bash
601
- bun dzql-compile entities/venues.sql -o compiled/
602
- ```
603
-
604
- 3. **Review generated SQL:**
605
- ```bash
606
- cat compiled/venues.sql
607
- ```
608
-
609
- 4. **Deploy:**
610
- ```bash
611
- psql < compiled/venues.sql
612
- ```
613
-
614
- 5. **Use:**
615
- ```sql
616
- SELECT save_venues('{"name": "MSG"}'::jsonb, 42);
617
- ```
618
-
619
- 6. **Debug:**
620
- ```sql
621
- EXPLAIN ANALYZE SELECT save_venues(...);
622
- \sf save_venues
623
- ```
624
-
625
- **Pros:**
626
- - ✅ Clear generated SQL
627
- - ✅ Standard debugging tools
628
- - ✅ Predictable performance
629
- - ✅ Git-trackable output
630
-
631
- **Cons:**
632
- - ❌ Build step required
633
- - ❌ Slower iteration
634
- - ❌ Less dynamic
635
-
636
- ---
637
-
638
- ## Summary Table
639
-
640
- | Aspect | Runtime | Compiled | Winner |
641
- |--------|---------|----------|--------|
642
- | **Performance** | Variable | Consistent | ✅ Compiled |
643
- | **Query Planning** | Generic | Specific | ✅ Compiled |
644
- | **Debugging** | Opaque | Transparent | ✅ Compiled |
645
- | **Development Speed** | Fast | Slower | ✅ Runtime |
646
- | **Type Safety** | None | Possible | ✅ Compiled |
647
- | **Optimization** | Hard | Easy | ✅ Compiled |
648
- | **Flexibility** | High | Medium | ✅ Runtime |
649
- | **Learning Curve** | Moderate | Low | ✅ Compiled |
650
- | **Production Readiness** | Good | Better | ✅ Compiled |
651
-
652
- ## Conclusion
653
-
654
- The compiled approach trades **development flexibility** for **production performance and clarity**.
655
-
656
- ### When to use Runtime (Current):
657
- - ✅ Rapid prototyping
658
- - ✅ Frequent schema changes
659
- - ✅ Learning DZQL
660
- - ✅ Small applications
661
-
662
- ### When to use Compiled (New):
663
- - ✅ Production applications
664
- - ✅ Performance-critical systems
665
- - ✅ Large teams (reviewable SQL)
666
- - ✅ Complex permission rules
667
- - ✅ Need to debug query plans
668
-
669
- ### Ideal Approach:
670
- **Use both!**
671
- - Development: Runtime for fast iteration
672
- - Staging: Compile and test
673
- - Production: Deploy compiled functions