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,158 +0,0 @@
1
- # Composite Primary Keys
2
-
3
- DZQL supports tables with composite (compound) primary keys. This guide explains how to register entities with composite keys and how the generated CRUD functions work.
4
-
5
- ## When to Use Composite Keys
6
-
7
- Composite primary keys are useful for:
8
-
9
- - **Junction tables** with additional data (beyond simple M2M)
10
- - **Position/state tables** keyed by entity type and ID
11
- - **Multi-tenant tables** keyed by tenant + entity
12
- - **Versioned records** keyed by ID + version
13
-
14
- ## Registering an Entity with a Composite Key
15
-
16
- To register an entity with a composite primary key, add `primary_key` to the `graph_rules` parameter:
17
-
18
- ```sql
19
- -- Create a table with composite primary key
20
- CREATE TABLE canvas_positions (
21
- entity_type VARCHAR(50) NOT NULL,
22
- entity_id INTEGER NOT NULL,
23
- x INTEGER NOT NULL,
24
- y INTEGER NOT NULL,
25
- width INTEGER DEFAULT 100,
26
- height INTEGER DEFAULT 100,
27
- PRIMARY KEY (entity_type, entity_id)
28
- );
29
-
30
- -- Register with DZQL using composite key
31
- SELECT dzql.register_entity(
32
- 'canvas_positions', -- table_name
33
- 'entity_type', -- label_field
34
- array['x', 'y', 'width', 'height'], -- searchable_fields
35
- '{}', -- fk_includes
36
- false, -- soft_delete
37
- '{}', -- temporal_fields
38
- '{}', -- notification_paths
39
- jsonb_build_object( -- permission_paths
40
- 'view', array[]::text[],
41
- 'create', array[]::text[],
42
- 'update', array[]::text[],
43
- 'delete', array[]::text[]
44
- ),
45
- jsonb_build_object( -- graph_rules
46
- 'primary_key', array['entity_type', 'entity_id']
47
- )
48
- );
49
- ```
50
-
51
- The `primary_key` array specifies the columns that form the composite key, in order.
52
-
53
- ## Generated Function Signatures
54
-
55
- When you compile an entity with a composite primary key, the generated functions have different signatures:
56
-
57
- ### GET Function
58
-
59
- ```sql
60
- -- Accepts JSONB with all PK fields
61
- SELECT get_canvas_positions(
62
- 1, -- user_id
63
- '{"entity_type": "node", "entity_id": 42}' -- composite PK as JSONB
64
- );
65
- ```
66
-
67
- ### SAVE Function
68
-
69
- ```sql
70
- -- Insert: provide all PK fields plus data
71
- SELECT save_canvas_positions(
72
- 1, -- user_id
73
- '{"entity_type": "node", "entity_id": 42, "x": 100, "y": 200}'
74
- );
75
-
76
- -- Update: same signature, existing record detected by PK
77
- SELECT save_canvas_positions(
78
- 1,
79
- '{"entity_type": "node", "entity_id": 42, "x": 150, "y": 250}'
80
- );
81
- ```
82
-
83
- The save function determines insert vs update by checking if a record with the composite key exists.
84
-
85
- ### DELETE Function
86
-
87
- ```sql
88
- -- Accepts JSONB with all PK fields
89
- SELECT delete_canvas_positions(
90
- 1, -- user_id
91
- '{"entity_type": "node", "entity_id": 42}' -- composite PK as JSONB
92
- );
93
- ```
94
-
95
- ### SEARCH Function
96
-
97
- Search works the same as simple PK entities - it returns paginated results with all fields.
98
-
99
- ## Type Casting
100
-
101
- DZQL automatically determines type casting for PK columns:
102
-
103
- - Columns named `id` or ending with `_id` are cast to `::int`
104
- - Other columns (like `entity_type`) are left as text
105
-
106
- This means for a key like `(entity_type, entity_id)`:
107
- - `entity_type` is compared as text
108
- - `entity_id` is cast to integer
109
-
110
- ## Events and Notifications
111
-
112
- Events for composite PK entities include the full composite key in the `pk` field:
113
-
114
- ```json
115
- {
116
- "table_name": "canvas_positions",
117
- "op": "insert",
118
- "pk": {"entity_type": "node", "entity_id": 42},
119
- "data": {"entity_type": "node", "entity_id": 42, "x": 100, "y": 200}
120
- }
121
- ```
122
-
123
- ## Limitations
124
-
125
- - **M2M relationships**: Tables with composite PKs can have M2M relationships, but this is an advanced use case. The M2M sync uses the first PK column for junction table lookups.
126
- - **Auto-increment**: Composite keys don't support auto-increment. All PK values must be provided on insert.
127
-
128
- ## Example: Template Dependencies
129
-
130
- A practical example - tracking dependencies between templates:
131
-
132
- ```sql
133
- CREATE TABLE template_dependencies (
134
- template_id INTEGER NOT NULL REFERENCES templates(id),
135
- depends_on_template_id INTEGER NOT NULL REFERENCES templates(id),
136
- dependency_type VARCHAR(20) DEFAULT 'requires',
137
- PRIMARY KEY (template_id, depends_on_template_id)
138
- );
139
-
140
- SELECT dzql.register_entity(
141
- 'template_dependencies',
142
- 'dependency_type',
143
- array['dependency_type'],
144
- '{"template": "templates", "depends_on": "templates"}',
145
- false,
146
- '{}',
147
- '{}',
148
- jsonb_build_object(
149
- 'view', array[]::text[],
150
- 'create', array[]::text[],
151
- 'update', array[]::text[],
152
- 'delete', array[]::text[]
153
- ),
154
- jsonb_build_object(
155
- 'primary_key', array['template_id', 'depends_on_template_id']
156
- )
157
- );
158
- ```
@@ -1,362 +0,0 @@
1
- # Custom Functions
2
-
3
- Add custom business logic to your entities with automatic compilation support.
4
-
5
- ## Overview
6
-
7
- DZQL's compiler now automatically includes custom SQL functions defined after entity registration. This eliminates the need to manually maintain functions in multiple locations.
8
-
9
- ## Benefits
10
-
11
- - **Single Source of Truth** - Define functions once in entity files
12
- - **No Manual Syncing** - Compiler handles everything
13
- - **Functions Stay With Entities** - Clear organization
14
- - **Automatic Registration** - Registry entries included
15
-
16
- ## How It Works
17
-
18
- ### Before (Manual Duplication)
19
-
20
- You had to maintain functions in two places:
21
-
22
- ```sql
23
- -- entities/calendar.sql (source)
24
- SELECT dzql.register_entity('tags', ...);
25
-
26
- CREATE FUNCTION toggle_resource_tag(...) RETURNS JSONB AS $$ ... $$;
27
- INSERT INTO dzql.registry (fn_regproc) VALUES ('toggle_resource_tag'::regproc);
28
- ```
29
-
30
- Then manually copy to:
31
-
32
- ```sql
33
- -- init_db/002_schema.sql (deployment)
34
- CREATE FUNCTION toggle_resource_tag(...) RETURNS JSONB AS $$ ... $$;
35
- INSERT INTO dzql.registry (fn_regproc) VALUES ('toggle_resource_tag'::regproc);
36
- ```
37
-
38
- **Problems:**
39
- - Functions exist in two places
40
- - Easy to forget to sync
41
- - Unclear which is source of truth
42
-
43
- ### After (Automatic Pass-through)
44
-
45
- Just define functions once after entity registration:
46
-
47
- ```sql
48
- -- entities/calendar.sql
49
- SELECT dzql.register_entity('tags', 'name', ...);
50
-
51
- -- Custom function - automatically passed through by compiler!
52
- CREATE OR REPLACE FUNCTION toggle_resource_tag(
53
- p_user_id INT,
54
- p_resource_id INT,
55
- p_tag_id INT
56
- ) RETURNS JSONB AS $$
57
- DECLARE
58
- v_exists BOOLEAN;
59
- BEGIN
60
- SELECT EXISTS(
61
- SELECT 1 FROM resource_tags
62
- WHERE resource_id = p_resource_id AND tag_id = p_tag_id
63
- ) INTO v_exists;
64
-
65
- IF v_exists THEN
66
- DELETE FROM resource_tags
67
- WHERE resource_id = p_resource_id AND tag_id = p_tag_id;
68
- ELSE
69
- INSERT INTO resource_tags (resource_id, tag_id)
70
- VALUES (p_resource_id, p_tag_id);
71
- END IF;
72
-
73
- RETURN jsonb_build_object('success', true, 'exists', NOT v_exists);
74
- END;
75
- $$ LANGUAGE plpgsql SECURITY DEFINER;
76
-
77
- -- Register for RPC access
78
- INSERT INTO dzql.registry (fn_regproc)
79
- VALUES ('toggle_resource_tag'::regproc);
80
- ```
81
-
82
- After `dzql compile`, the custom function automatically appears in:
83
- - `init_db/tags.sql` under "Custom Functions" section
84
-
85
- ## What Gets Passed Through
86
-
87
- The compiler extracts:
88
-
89
- 1. **CREATE FUNCTION statements**
90
- ```sql
91
- CREATE FUNCTION my_function(...) RETURNS ... AS $$ ... $$;
92
- CREATE OR REPLACE FUNCTION my_function(...) RETURNS ... AS $$ ... $$;
93
- ```
94
-
95
- 2. **Registry registrations**
96
- ```sql
97
- INSERT INTO dzql.registry (fn_regproc) VALUES ('my_function'::regproc);
98
- ```
99
-
100
- 3. **Alternative registration syntax** (if you use it)
101
- ```sql
102
- SELECT dzql.register_function('my_function');
103
- ```
104
-
105
- ## Scope
106
-
107
- Custom functions are extracted from **after** the entity registration until:
108
- - The next `dzql.register_entity()` call, OR
109
- - End of file
110
-
111
- ```sql
112
- -- Entity 1
113
- SELECT dzql.register_entity('users', ...);
114
- CREATE FUNCTION user_helper() ...; -- Included with users entity
115
-
116
- -- Entity 2
117
- SELECT dzql.register_entity('posts', ...);
118
- CREATE FUNCTION post_helper() ...; -- Included with posts entity
119
- ```
120
-
121
- Each entity gets its own custom functions isolated in the compiled output.
122
-
123
- ## Compiled Output Format
124
-
125
- ```sql
126
- -- ============================================================================
127
- -- DZQL Compiled Functions for: tags
128
- -- Generated: 2025-11-20T15:00:00.000Z
129
- -- ============================================================================
130
-
131
- -- [Generated CRUD functions: get_tags, save_tags, delete_tags, etc.]
132
-
133
- -- ============================================================================
134
- -- Custom Functions for: tags
135
- -- Pass-through from entity definition
136
- -- ============================================================================
137
-
138
- CREATE OR REPLACE FUNCTION toggle_resource_tag(...) ...;
139
-
140
- INSERT INTO dzql.registry (fn_regproc) VALUES ('toggle_resource_tag'::regproc);
141
- ```
142
-
143
- ## Use Cases
144
-
145
- ### Toggle Relationships
146
-
147
- ```sql
148
- CREATE OR REPLACE FUNCTION toggle_favorite(
149
- p_user_id INT,
150
- p_item_id INT
151
- ) RETURNS JSONB AS $$
152
- -- Toggle logic here
153
- $$ LANGUAGE plpgsql SECURITY DEFINER;
154
- ```
155
-
156
- ### Computed Fields
157
-
158
- ```sql
159
- CREATE OR REPLACE FUNCTION calculate_item_score(
160
- p_user_id INT,
161
- p_item_id INT
162
- ) RETURNS JSONB AS $$
163
- -- Scoring logic here
164
- $$ LANGUAGE plpgsql;
165
- ```
166
-
167
- ### Bulk Operations
168
-
169
- ```sql
170
- CREATE OR REPLACE FUNCTION bulk_assign_tags(
171
- p_user_id INT,
172
- p_resource_ids INT[],
173
- p_tag_id INT
174
- ) RETURNS JSONB AS $$
175
- -- Bulk assignment here
176
- $$ LANGUAGE plpgsql SECURITY DEFINER;
177
- ```
178
-
179
- ### Complex Business Logic
180
-
181
- ```sql
182
- CREATE OR REPLACE FUNCTION approve_workflow(
183
- p_user_id INT,
184
- p_document_id INT
185
- ) RETURNS JSONB AS $$
186
- -- Multi-step approval logic
187
- $$ LANGUAGE plpgsql SECURITY DEFINER;
188
- ```
189
-
190
- ## Best Practices
191
-
192
- ### 1. Keep Functions Close to Entities
193
-
194
- Define functions right after the related entity registration:
195
-
196
- ```sql
197
- SELECT dzql.register_entity('documents', ...);
198
-
199
- -- Related custom functions
200
- CREATE FUNCTION approve_document(...) ...;
201
- CREATE FUNCTION reject_document(...) ...;
202
- ```
203
-
204
- ### 2. Always Register Functions
205
-
206
- Don't forget to register for RPC access:
207
-
208
- ```sql
209
- CREATE FUNCTION my_function(...) ...;
210
-
211
- -- Required for client access!
212
- INSERT INTO dzql.registry (fn_regproc) VALUES ('my_function'::regproc);
213
- ```
214
-
215
- ### 3. Use SECURITY DEFINER Carefully
216
-
217
- Only use `SECURITY DEFINER` when the function needs elevated privileges:
218
-
219
- ```sql
220
- -- Needs elevated privileges (good use of SECURITY DEFINER)
221
- CREATE FUNCTION admin_delete_user(...)
222
- RETURNS void AS $$ ... $$
223
- LANGUAGE plpgsql SECURITY DEFINER;
224
-
225
- -- User operates on own data (use SECURITY INVOKER or default)
226
- CREATE FUNCTION update_profile(...)
227
- RETURNS jsonb AS $$ ... $$
228
- LANGUAGE plpgsql; -- SECURITY INVOKER is default
229
- ```
230
-
231
- ### 4. Include Permission Checks
232
-
233
- Custom functions should validate permissions:
234
-
235
- ```sql
236
- CREATE OR REPLACE FUNCTION custom_operation(
237
- p_user_id INT,
238
- p_item_id INT
239
- ) RETURNS JSONB AS $$
240
- BEGIN
241
- -- Always check permission first!
242
- IF NOT dzql.check_permission(p_user_id, 'update', 'items',
243
- (SELECT to_jsonb(items.*) FROM items WHERE id = p_item_id)
244
- ) THEN
245
- RAISE EXCEPTION 'Permission denied';
246
- END IF;
247
-
248
- -- Business logic here
249
- ...
250
- END;
251
- $$ LANGUAGE plpgsql SECURITY DEFINER;
252
- ```
253
-
254
- ## Limitations
255
-
256
- ### Functions Not Related to Entities
257
-
258
- If you have utility functions not tied to a specific entity, you have two options:
259
-
260
- **Option 1:** Create a dedicated entity file
261
- ```sql
262
- -- entities/utils.sql
263
- -- No entity registration, just functions
264
-
265
- CREATE FUNCTION general_utility(...) ...;
266
- INSERT INTO dzql.registry (fn_regproc) VALUES ('general_utility'::regproc);
267
- ```
268
-
269
- **Option 2:** Keep in manual migration file
270
- ```sql
271
- -- init_db/002_utilities.sql
272
- CREATE FUNCTION general_utility(...) ...;
273
- ```
274
-
275
- ### Detection Logic
276
-
277
- The compiler extracts SQL between entity registrations. Only these patterns are recognized:
278
-
279
- - `CREATE FUNCTION ...` or `CREATE OR REPLACE FUNCTION ...`
280
- - `INSERT INTO dzql.registry ...`
281
- - `SELECT dzql.register_function(...)`
282
-
283
- Other SQL (like `CREATE TYPE`, `CREATE INDEX`) is **not** automatically passed through.
284
-
285
- ## Migration
286
-
287
- ### For Existing Projects
288
-
289
- If you already have custom functions duplicated:
290
-
291
- 1. **Keep functions in entity files** (e.g., `entities/calendar.sql`)
292
- 2. **Remove from manual migration files** (e.g., `init_db/002_schema.sql`)
293
- 3. **Recompile:** `dzql compile entities/*.sql`
294
- 4. **Deploy:** Updated compiled functions
295
-
296
- Your entity files become the single source of truth!
297
-
298
- ## Example: Complete Entity with Custom Functions
299
-
300
- ```sql
301
- -- entities/resources.sql
302
-
303
- -- Create table
304
- CREATE TABLE resources (
305
- id serial PRIMARY KEY,
306
- org_id integer REFERENCES organisations(id),
307
- title text NOT NULL,
308
- description text,
309
- owner_id integer REFERENCES users(id)
310
- );
311
-
312
- -- Register entity
313
- SELECT dzql.register_entity(
314
- 'resources',
315
- 'title',
316
- ARRAY['title', 'description'],
317
- '{"org": "organisations"}',
318
- false,
319
- '{}',
320
- '{}',
321
- '{"view": [], "create": [], "update": ["@owner_id"], "delete": ["@owner_id"]}',
322
- '{}',
323
- '{"owner_id": "@user_id"}'
324
- );
325
-
326
- -- Custom function (automatically passed through)
327
- CREATE OR REPLACE FUNCTION assign_resource_to_user(
328
- p_user_id INT,
329
- p_resource_id INT,
330
- p_target_user_id INT
331
- ) RETURNS JSONB
332
- LANGUAGE plpgsql SECURITY DEFINER AS $$
333
- BEGIN
334
- -- Check permission
335
- IF NOT dzql.check_permission(p_user_id, 'update', 'resources',
336
- (SELECT to_jsonb(resources.*) FROM resources WHERE id = p_resource_id)
337
- ) THEN
338
- RAISE EXCEPTION 'Permission denied';
339
- END IF;
340
-
341
- -- Update owner
342
- UPDATE resources
343
- SET owner_id = p_target_user_id
344
- WHERE id = p_resource_id;
345
-
346
- -- Return updated resource
347
- RETURN (SELECT to_jsonb(resources.*) FROM resources WHERE id = p_resource_id);
348
- END;
349
- $$;
350
-
351
- -- Register for RPC access
352
- INSERT INTO dzql.registry (fn_regproc)
353
- VALUES ('assign_resource_to_user'::regproc);
354
- ```
355
-
356
- After compilation, everything is in `init_db/resources.sql` - no manual copying needed!
357
-
358
- ## See Also
359
-
360
- - [Many-to-Many Support](./many-to-many.md) - M2M relationships eliminate many toggle functions
361
- - [Field Defaults](./field-defaults.md) - Auto-populate fields
362
- - [Graph Rules](../reference/api.md#graph-rules) - Automatic relationship management