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,183 +0,0 @@
1
- # Advanced SEARCH Filters
2
-
3
- The compiled SEARCH functions support powerful JSONB-based filtering with multiple operators.
4
-
5
- ## Filter Operators
6
-
7
- ### Comparison Operators
8
-
9
- **Equal (`eq`)** - Exact match
10
- ```json
11
- { "status": { "eq": "active" } }
12
- ```
13
- or simplified:
14
- ```json
15
- { "status": "active" }
16
- ```
17
-
18
- **Not Equal (`ne`)** - Exclude values
19
- ```json
20
- { "status": { "ne": "deleted" } }
21
- ```
22
-
23
- **Greater Than (`gt`)**
24
- ```json
25
- { "age": { "gt": 18 } }
26
- ```
27
-
28
- **Greater Than or Equal (`gte`)**
29
- ```json
30
- { "age": { "gte": 18 } }
31
- ```
32
-
33
- **Less Than (`lt`)**
34
- ```json
35
- { "created_at": { "lt": "2024-01-01" } }
36
- ```
37
-
38
- **Less Than or Equal (`lte`)**
39
- ```json
40
- { "price": { "lte": 100 } }
41
- ```
42
-
43
- ### Array Membership
44
-
45
- **In (`in`)** - Match any value in array
46
- ```json
47
- { "status": { "in": ["active", "pending", "review"] } }
48
- ```
49
-
50
- ### Pattern Matching
51
-
52
- **Case-Insensitive Like (`ilike`)** - PostgreSQL pattern matching
53
- ```json
54
- { "email": { "ilike": "%@gmail.com" } }
55
- ```
56
-
57
- **Like (`like`)** - Case-sensitive pattern matching
58
- ```json
59
- { "code": { "like": "PRD-%" } }
60
- ```
61
-
62
- ## Combining Multiple Filters
63
-
64
- You can combine multiple filters on different fields:
65
-
66
- ```json
67
- {
68
- "age": { "gte": 18, "lte": 65 },
69
- "status": { "in": ["active", "pending"] },
70
- "email": { "ilike": "%@company.com" }
71
- }
72
- ```
73
-
74
- You can also apply multiple operators to the same field:
75
-
76
- ```json
77
- {
78
- "age": { "gte": 18, "lt": 65 }
79
- }
80
- ```
81
-
82
- ## Example Usage
83
-
84
- ### Simple equality filter
85
- ```javascript
86
- const result = await sql`
87
- SELECT search_users(
88
- p_filters := '{"status": "active"}'::jsonb,
89
- p_page := 1,
90
- p_limit := 25
91
- )
92
- `;
93
- ```
94
-
95
- ### Age range filter
96
- ```javascript
97
- const result = await sql`
98
- SELECT search_users(
99
- p_filters := '{"age": {"gte": 18, "lte": 65}}'::jsonb
100
- )
101
- `;
102
- ```
103
-
104
- ### Multiple status filter
105
- ```javascript
106
- const result = await sql`
107
- SELECT search_orders(
108
- p_filters := '{"status": {"in": ["pending", "processing", "shipped"]}}'::jsonb
109
- )
110
- `;
111
- ```
112
-
113
- ### Email domain filter with text search
114
- ```javascript
115
- const result = await sql`
116
- SELECT search_users(
117
- p_filters := '{"email": {"ilike": "%@company.com"}}'::jsonb,
118
- p_search := 'john',
119
- p_sort := '{"field": "created_at", "order": "desc"}'::jsonb
120
- )
121
- `;
122
- ```
123
-
124
- ### Complex combined filters
125
- ```javascript
126
- const result = await sql`
127
- SELECT search_products(
128
- p_filters := '{
129
- "price": {"gte": 10, "lte": 100},
130
- "category": {"in": ["electronics", "accessories"]},
131
- "status": "active",
132
- "name": {"ilike": "%wireless%"}
133
- }'::jsonb,
134
- p_page := 1,
135
- p_limit := 50
136
- )
137
- `;
138
- ```
139
-
140
- ## Generated SQL
141
-
142
- The filters are converted to optimized SQL WHERE conditions:
143
-
144
- ```sql
145
- -- Input filters:
146
- { "age": {"gte": 18}, "status": {"in": ["active", "pending"]} }
147
-
148
- -- Generated WHERE clause:
149
- WHERE TRUE
150
- AND age >= '18'
151
- AND status = ANY(ARRAY['active', 'pending']::TEXT[])
152
- ```
153
-
154
- ## Performance Considerations
155
-
156
- 1. **Index your filter fields** - Create indexes on frequently filtered columns:
157
- ```sql
158
- CREATE INDEX idx_users_status ON users(status);
159
- CREATE INDEX idx_users_age ON users(age);
160
- ```
161
-
162
- 2. **Use exact matches when possible** - `eq` is faster than `ilike`
163
-
164
- 3. **Limit ILIKE patterns** - Patterns starting with `%` can't use indexes efficiently
165
-
166
- 4. **Combine with pagination** - Always use `p_limit` to avoid loading large result sets
167
-
168
- ## Type Safety
169
-
170
- The filter system is type-aware and handles:
171
- - **Strings** - Quoted and escaped properly
172
- - **Numbers** - Cast appropriately
173
- - **Booleans** - Converted to SQL boolean values
174
- - **Dates** - Handled as timestamp comparisons
175
- - **Arrays** - Expanded for `IN` clauses
176
-
177
- ## Error Handling
178
-
179
- Unknown operators are silently ignored to prevent SQL injection. Only these operators are supported:
180
- - `eq`, `ne`
181
- - `gt`, `gte`, `lt`, `lte`
182
- - `in`
183
- - `like`, `ilike`
@@ -1,415 +0,0 @@
1
- # DZQL Compiler - Coding Standards
2
-
3
- This document defines the coding standards that the DZQL Compiler enforces when generating PostgreSQL functions.
4
-
5
- ## Function Naming Conventions
6
-
7
- ### 1. Parameter Naming
8
-
9
- **All parameters MUST use the `p_` prefix:**
10
-
11
- ```sql
12
- -- ✅ CORRECT
13
- CREATE FUNCTION get_users(
14
- p_user_id INT,
15
- p_id INT,
16
- p_on_date TIMESTAMPTZ DEFAULT NULL
17
- )
18
-
19
- -- ❌ WRONG
20
- CREATE FUNCTION get_users(
21
- user_id INT,
22
- id INT,
23
- on_date TIMESTAMPTZ DEFAULT NULL
24
- )
25
- ```
26
-
27
- ### 2. Parameter Ordering
28
-
29
- **`p_user_id INT` MUST be the first parameter in ALL functions:**
30
-
31
- This is a critical security requirement from the DZQL framework - the authenticated user ID must always be the first parameter.
32
-
33
- ```sql
34
- -- ✅ CORRECT - p_user_id first
35
- CREATE FUNCTION get_users(p_user_id INT, p_id INT, ...)
36
- CREATE FUNCTION save_users(p_user_id INT, p_data JSONB)
37
- CREATE FUNCTION delete_users(p_user_id INT, p_id INT)
38
- CREATE FUNCTION lookup_users(p_user_id INT, p_filter TEXT, ...)
39
- CREATE FUNCTION search_users(p_user_id INT, p_filters JSONB, ...)
40
-
41
- -- ❌ WRONG - p_user_id not first
42
- CREATE FUNCTION get_users(p_id INT, p_user_id INT, ...)
43
- CREATE FUNCTION save_users(p_data JSONB, p_user_id INT)
44
- ```
45
-
46
- ### 3. Helper Function Prefixes
47
-
48
- **Helper functions MUST start with underscore `_` to prevent direct websocket access:**
49
-
50
- Helper functions are internal implementation details that should not be callable by websocket clients.
51
-
52
- ```sql
53
- -- ✅ CORRECT - Helper functions with underscore
54
- CREATE FUNCTION _graph_users_on_create(p_user_id INT, p_record JSONB)
55
- CREATE FUNCTION _resolve_notification_paths_users(p_user_id INT, p_record JSONB)
56
-
57
- -- ❌ WRONG - Helper functions without underscore (publicly callable!)
58
- CREATE FUNCTION graph_users_on_create(p_record JSONB, p_user_id INT)
59
- CREATE FUNCTION resolve_notification_paths_users(p_record JSONB)
60
- ```
61
-
62
- **Public API functions do NOT use underscore prefix:**
63
-
64
- ```sql
65
- -- ✅ CORRECT - Public API functions
66
- CREATE FUNCTION can_view_users(p_user_id INT, p_record JSONB)
67
- CREATE FUNCTION can_create_users(p_user_id INT, p_record JSONB)
68
- CREATE FUNCTION get_users(p_user_id INT, p_id INT, ...)
69
- CREATE FUNCTION save_users(p_user_id INT, p_data JSONB)
70
- CREATE FUNCTION delete_users(p_user_id INT, p_id INT)
71
- CREATE FUNCTION lookup_users(p_user_id INT, p_filter TEXT, ...)
72
- CREATE FUNCTION search_users(p_user_id INT, p_filters JSONB, ...)
73
- ```
74
-
75
- ## JSON vs JSONB for Function Parameters
76
-
77
- ### External API Parameters: Use JSON
78
-
79
- When defining function parameters that accept JSON from external callers (API boundary), use `JSON` type (text-based) rather than `JSONB`. This allows callers to pass `JSON.stringify(options)` as a plain string without needing special serialization like `sql.json()`.
80
-
81
- ```sql
82
- -- ✅ CORRECT - JSON for external input parameters
83
- CREATE FUNCTION register_user(
84
- p_email TEXT,
85
- p_password TEXT,
86
- p_options JSON DEFAULT NULL -- Accepts plain JSON string from API
87
- )
88
-
89
- -- ❌ WRONG - JSONB requires special serialization from clients
90
- CREATE FUNCTION register_user(
91
- p_email TEXT,
92
- p_password TEXT,
93
- p_options JSONB DEFAULT NULL -- Harder to call from JavaScript
94
- )
95
- ```
96
-
97
- ### Internal Operations: Cast to JSONB
98
-
99
- Inside the function, cast to JSONB if you need JSONB operators (`->`, `->>`, `-`, `||`, `?`, etc.):
100
-
101
- ```sql
102
- CREATE FUNCTION register_user(p_email TEXT, p_password TEXT, p_options JSON DEFAULT NULL)
103
- RETURNS JSONB AS $$
104
- DECLARE
105
- v_insert_data JSONB;
106
- BEGIN
107
- v_insert_data := jsonb_build_object('email', p_email);
108
-
109
- IF p_options IS NOT NULL THEN
110
- -- Cast to JSONB for internal operations
111
- v_insert_data := (p_options::jsonb - 'id' - 'password') || v_insert_data;
112
- END IF;
113
-
114
- -- ...
115
- END;
116
- $$ LANGUAGE plpgsql;
117
- ```
118
-
119
- ### Table Columns: Use JSONB
120
-
121
- Table columns should still use `JSONB` for efficient storage and indexing:
122
-
123
- ```sql
124
- -- ✅ CORRECT - JSONB for table columns
125
- CREATE TABLE users (
126
- id SERIAL PRIMARY KEY,
127
- metadata JSONB DEFAULT '{}' -- Efficient storage & indexing
128
- );
129
- ```
130
-
131
- ### Summary
132
-
133
- | Context | Type | Reason |
134
- |---------|------|--------|
135
- | Function input parameters | `JSON` | Easy to pass from JavaScript (`JSON.stringify()`) |
136
- | Internal function operations | `::jsonb` cast | Access to JSONB operators |
137
- | Table columns | `JSONB` | Efficient storage and indexing |
138
-
139
- This pattern - **JSON for input parameters, JSONB for storage** - eliminates serialization confusion at the API boundary.
140
-
141
- ## Function Categories
142
-
143
- ### Public API Functions (No underscore)
144
-
145
- These are directly callable via the DZQL websocket API:
146
-
147
- 1. **Permission Check Functions** - `can_{operation}_{table}`
148
- - `can_view_{table}(p_user_id INT, p_record JSONB)`
149
- - `can_create_{table}(p_user_id INT, p_record JSONB)`
150
- - `can_update_{table}(p_user_id INT, p_record JSONB)`
151
- - `can_delete_{table}(p_user_id INT, p_record JSONB)`
152
-
153
- 2. **CRUD Operation Functions**
154
- - `get_{table}(p_user_id INT, p_id INT, p_on_date TIMESTAMPTZ DEFAULT NULL)`
155
- - `save_{table}(p_user_id INT, p_data JSONB)`
156
- - `delete_{table}(p_user_id INT, p_id INT)`
157
- - `lookup_{table}(p_user_id INT, p_filter TEXT DEFAULT NULL, p_limit INT DEFAULT 50)`
158
- - `search_{table}(p_user_id INT, p_filters JSONB DEFAULT '{}', ...)`
159
-
160
- ### Helper Functions (With underscore)
161
-
162
- These are internal and NOT directly callable via websocket:
163
-
164
- 1. **Graph Rule Functions** - `_graph_{table}_{trigger}`
165
- - `_graph_{table}_on_create(p_user_id INT, p_record JSONB)`
166
- - `_graph_{table}_on_update(p_user_id INT, p_old_record JSONB, p_new_record JSONB)`
167
- - `_graph_{table}_on_delete(p_user_id INT, p_old_record JSONB)`
168
-
169
- 2. **Notification Resolution Functions** - `_resolve_notification_paths_{table}`
170
- - `_resolve_notification_paths_{table}(p_user_id INT, p_record JSONB)`
171
-
172
- ## Standard Permission Functions
173
-
174
- **ALL entities MUST have all 4 permission check functions generated:**
175
-
176
- Even if an entity has no permission restrictions (public access), all 4 functions must exist:
177
-
178
- ```sql
179
- -- Public access - returns true
180
- CREATE FUNCTION can_view_users(p_user_id INT, p_record JSONB)
181
- RETURNS BOOLEAN AS $$
182
- BEGIN
183
- RETURN true; -- Public access
184
- END;
185
- $$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
186
-
187
- -- Restricted access - checks permissions
188
- CREATE FUNCTION can_update_users(p_user_id INT, p_record JSONB)
189
- RETURNS BOOLEAN AS $$
190
- BEGIN
191
- RETURN EXISTS (
192
- SELECT 1 FROM acts_for
193
- WHERE acts_for.org_id = (p_record->>'org_id')::int
194
- AND acts_for.user_id = p_user_id
195
- AND acts_for.valid_to IS NULL
196
- );
197
- END;
198
- $$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
199
- ```
200
-
201
- ## Variable Naming
202
-
203
- ### Local Variables
204
-
205
- **Use `v_` prefix for all local variables:**
206
-
207
- ```sql
208
- DECLARE
209
- v_result users%ROWTYPE; -- ✅ CORRECT
210
- v_existing users%ROWTYPE; -- ✅ CORRECT
211
- v_is_insert BOOLEAN := false; -- ✅ CORRECT
212
- v_notify_users INT[]; -- ✅ CORRECT
213
- BEGIN
214
- -- ...
215
- END;
216
- ```
217
-
218
- ## SQL Style
219
-
220
- ### Keywords
221
-
222
- **All SQL keywords should be UPPERCASE:**
223
-
224
- ```sql
225
- -- ✅ CORRECT
226
- CREATE OR REPLACE FUNCTION get_users(...)
227
- RETURNS JSONB AS $$
228
- DECLARE
229
- v_result JSONB;
230
- BEGIN
231
- SELECT * INTO v_result FROM users WHERE id = p_id;
232
- RETURN v_result;
233
- END;
234
- $$ LANGUAGE plpgsql SECURITY DEFINER;
235
-
236
- -- ❌ WRONG
237
- create or replace function get_users(...)
238
- returns jsonb as $$
239
- declare
240
- v_result jsonb;
241
- begin
242
- select * into v_result from users where id = p_id;
243
- return v_result;
244
- end;
245
- $$ language plpgsql security definer;
246
- ```
247
-
248
- ### Function Attributes
249
-
250
- **All functions MUST have:**
251
- - `LANGUAGE plpgsql` (or `LANGUAGE sql`)
252
- - `SECURITY DEFINER` - Runs with privileges of the function owner
253
-
254
- **Permission functions should additionally have:**
255
- - `STABLE` - Indicates function doesn't modify the database
256
-
257
- ```sql
258
- -- Permission function
259
- CREATE FUNCTION can_view_users(...)
260
- RETURNS BOOLEAN AS $$
261
- -- ...
262
- $$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
263
-
264
- -- Operation function
265
- CREATE FUNCTION save_users(...)
266
- RETURNS JSONB AS $$
267
- -- ...
268
- $$ LANGUAGE plpgsql SECURITY DEFINER;
269
- ```
270
-
271
- ## Complete Example
272
-
273
- Here's a complete example showing all coding standards:
274
-
275
- ```sql
276
- -- ============================================================================
277
- -- Permission Functions (Public API - no underscore)
278
- -- ============================================================================
279
-
280
- -- Permission check: view on organisations
281
- CREATE OR REPLACE FUNCTION can_view_organisations(
282
- p_user_id INT,
283
- p_record JSONB
284
- ) RETURNS BOOLEAN AS $$
285
- BEGIN
286
- RETURN true; -- Public access
287
- END;
288
- $$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
289
-
290
- -- ============================================================================
291
- -- CRUD Operations (Public API - no underscore)
292
- -- ============================================================================
293
-
294
- -- GET operation for organisations
295
- CREATE OR REPLACE FUNCTION get_organisations(
296
- p_user_id INT,
297
- p_id INT,
298
- p_on_date TIMESTAMPTZ DEFAULT NULL
299
- ) RETURNS JSONB AS $$
300
- DECLARE
301
- v_result JSONB;
302
- v_record organisations%ROWTYPE;
303
- BEGIN
304
- -- Fetch the record
305
- SELECT * INTO v_record FROM organisations WHERE id = p_id;
306
-
307
- IF NOT FOUND THEN
308
- RAISE EXCEPTION 'Record not found: % with id=%', 'organisations', p_id;
309
- END IF;
310
-
311
- -- Convert to JSONB
312
- v_result := to_jsonb(v_record);
313
-
314
- -- Check view permission
315
- IF NOT can_view_organisations(p_user_id, v_result) THEN
316
- RAISE EXCEPTION 'Permission denied: view on organisations';
317
- END IF;
318
-
319
- RETURN v_result;
320
- END;
321
- $$ LANGUAGE plpgsql SECURITY DEFINER;
322
-
323
- -- SAVE operation for organisations
324
- CREATE OR REPLACE FUNCTION save_organisations(
325
- p_user_id INT,
326
- p_data JSONB
327
- ) RETURNS JSONB AS $$
328
- DECLARE
329
- v_result organisations%ROWTYPE;
330
- v_is_insert BOOLEAN;
331
- BEGIN
332
- -- Perform UPSERT
333
- -- ... implementation ...
334
-
335
- -- Call graph rules helper
336
- IF v_is_insert THEN
337
- PERFORM _graph_organisations_on_create(p_user_id, to_jsonb(v_result));
338
- END IF;
339
-
340
- RETURN to_jsonb(v_result);
341
- END;
342
- $$ LANGUAGE plpgsql SECURITY DEFINER;
343
-
344
- -- ============================================================================
345
- -- Helper Functions (Internal - with underscore)
346
- -- ============================================================================
347
-
348
- -- Graph rules: on_create on organisations
349
- CREATE OR REPLACE FUNCTION _graph_organisations_on_create(
350
- p_user_id INT,
351
- p_record JSONB
352
- ) RETURNS VOID AS $$
353
- BEGIN
354
- -- Creator becomes owner
355
- INSERT INTO acts_for (user_id, org_id, valid_from)
356
- VALUES (p_user_id, (p_record->>'id'), CURRENT_DATE);
357
- END;
358
- $$ LANGUAGE plpgsql SECURITY DEFINER;
359
-
360
- -- Notification path resolution for organisations
361
- CREATE OR REPLACE FUNCTION _resolve_notification_paths_organisations(
362
- p_user_id INT,
363
- p_record JSONB
364
- ) RETURNS INT[] AS $$
365
- DECLARE
366
- v_users INT[] := ARRAY[]::INT[];
367
- BEGIN
368
- -- Resolve notification recipients
369
- -- ... implementation ...
370
-
371
- RETURN ARRAY(SELECT DISTINCT unnest(v_users));
372
- END;
373
- $$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
374
- ```
375
-
376
- ## Why These Standards Matter
377
-
378
- ### Security
379
-
380
- 1. **`p_user_id` first** - Ensures user context is always explicit and hard to forget
381
- 2. **Helper functions with `_`** - Prevents direct websocket access to internal functions
382
- 3. **SECURITY DEFINER** - Ensures proper privilege execution
383
-
384
- ### Consistency
385
-
386
- 1. **Predictable parameter order** - Makes all functions follow the same pattern
387
- 2. **Standard naming** - Makes generated code easy to read and maintain
388
- 3. **All 4 permission functions** - Ensures complete access control coverage
389
-
390
- ### WebSocket Safety
391
-
392
- The underscore prefix prevents clients from directly calling helper functions:
393
-
394
- ```javascript
395
- // ✅ These work - public API
396
- await ws.api.get.users({id: 1});
397
- await ws.api.save.users({name: 'John'});
398
-
399
- // ❌ These DON'T work - helper functions blocked
400
- await ws.api._graph_users_on_create({...}); // Blocked!
401
- await ws.api._resolve_notification_paths_users({...}); // Blocked!
402
- ```
403
-
404
- ## Validation
405
-
406
- The DZQL Compiler enforces these standards automatically. All generated code is validated by comprehensive tests that verify:
407
-
408
- - ✅ Parameter naming and ordering
409
- - ✅ Function prefixes (helper vs public)
410
- - ✅ All 4 permission functions exist
411
- - ✅ SQL syntax correctness
412
- - ✅ SECURITY DEFINER attribute presence
413
- - ✅ Uppercase SQL keywords
414
-
415
- See `tests/sql-validation.test.js` for complete validation suite.