dzql 0.5.32 → 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.
- package/.env.sample +28 -0
- package/compose.yml +28 -0
- package/dist/client/index.ts +1 -0
- package/dist/client/stores/useMyProfileStore.ts +114 -0
- package/dist/client/stores/useOrgDashboardStore.ts +131 -0
- package/dist/client/stores/useVenueDetailStore.ts +117 -0
- package/dist/client/ws.ts +716 -0
- package/dist/db/migrations/000_core.sql +92 -0
- package/dist/db/migrations/20251229T212912022Z_schema.sql +3020 -0
- package/dist/db/migrations/20251229T212912022Z_subscribables.sql +371 -0
- package/dist/runtime/manifest.json +1562 -0
- package/docs/README.md +293 -36
- package/docs/feature-requests/applyPatch-bug-report.md +85 -0
- package/docs/feature-requests/connection-ready-profile.md +57 -0
- package/docs/feature-requests/hidden-bug-report.md +111 -0
- package/docs/feature-requests/hidden-fields-subscribables.md +34 -0
- package/docs/feature-requests/subscribable-param-key-bug.md +38 -0
- package/docs/feature-requests/todo.md +146 -0
- package/docs/for_ai.md +641 -0
- package/docs/project-setup.md +432 -0
- package/examples/blog.ts +50 -0
- package/examples/invalid.ts +18 -0
- package/examples/venues.js +485 -0
- package/package.json +23 -60
- package/src/cli/codegen/client.ts +99 -0
- package/src/cli/codegen/manifest.ts +95 -0
- package/src/cli/codegen/pinia.ts +174 -0
- package/src/cli/codegen/realtime.ts +58 -0
- package/src/cli/codegen/sql.ts +698 -0
- package/src/cli/codegen/subscribable_sql.ts +547 -0
- package/src/cli/codegen/subscribable_store.ts +184 -0
- package/src/cli/codegen/types.ts +142 -0
- package/src/cli/compiler/analyzer.ts +52 -0
- package/src/cli/compiler/graph_rules.ts +251 -0
- package/src/cli/compiler/ir.ts +233 -0
- package/src/cli/compiler/loader.ts +132 -0
- package/src/cli/compiler/permissions.ts +227 -0
- package/src/cli/index.ts +164 -0
- package/src/client/index.ts +1 -0
- package/src/client/ws.ts +286 -0
- package/src/create/.env.example +8 -0
- package/src/create/README.md +101 -0
- package/src/create/compose.yml +14 -0
- package/src/create/domain.ts +153 -0
- package/src/create/package.json +24 -0
- package/src/create/server.ts +18 -0
- package/src/create/setup.sh +11 -0
- package/src/create/tsconfig.json +15 -0
- package/src/runtime/auth.ts +39 -0
- package/src/runtime/db.ts +33 -0
- package/src/runtime/errors.ts +51 -0
- package/src/runtime/index.ts +98 -0
- package/src/runtime/js_functions.ts +63 -0
- package/src/runtime/manifest_loader.ts +29 -0
- package/src/runtime/namespace.ts +483 -0
- package/src/runtime/server.ts +87 -0
- package/src/runtime/ws.ts +197 -0
- package/src/shared/ir.ts +197 -0
- package/tests/client.test.ts +38 -0
- package/tests/codegen.test.ts +71 -0
- package/tests/compiler.test.ts +45 -0
- package/tests/graph_rules.test.ts +173 -0
- package/tests/integration/db.test.ts +174 -0
- package/tests/integration/e2e.test.ts +65 -0
- package/tests/integration/features.test.ts +922 -0
- package/tests/integration/full_stack.test.ts +262 -0
- package/tests/integration/setup.ts +45 -0
- package/tests/ir.test.ts +32 -0
- package/tests/namespace.test.ts +395 -0
- package/tests/permissions.test.ts +55 -0
- package/tests/pinia.test.ts +48 -0
- package/tests/realtime.test.ts +22 -0
- package/tests/runtime.test.ts +80 -0
- package/tests/subscribable_gen.test.ts +72 -0
- package/tests/subscribable_reactivity.test.ts +258 -0
- package/tests/venues_gen.test.ts +25 -0
- package/tsconfig.json +20 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/README.md +0 -90
- package/bin/cli.js +0 -727
- package/docs/compiler/ADVANCED_FILTERS.md +0 -183
- package/docs/compiler/CODING_STANDARDS.md +0 -415
- package/docs/compiler/COMPARISON.md +0 -673
- package/docs/compiler/QUICKSTART.md +0 -326
- package/docs/compiler/README.md +0 -134
- package/docs/examples/README.md +0 -38
- package/docs/examples/blog.sql +0 -160
- package/docs/examples/venue-detail-simple.sql +0 -8
- package/docs/examples/venue-detail-subscribable.sql +0 -45
- package/docs/for-ai/claude-guide.md +0 -1210
- package/docs/getting-started/quickstart.md +0 -125
- package/docs/getting-started/subscriptions-quick-start.md +0 -203
- package/docs/getting-started/tutorial.md +0 -1104
- package/docs/guides/atomic-updates.md +0 -299
- package/docs/guides/client-stores.md +0 -730
- package/docs/guides/composite-primary-keys.md +0 -158
- package/docs/guides/custom-functions.md +0 -362
- package/docs/guides/drop-semantics.md +0 -554
- package/docs/guides/field-defaults.md +0 -240
- package/docs/guides/interpreter-vs-compiler.md +0 -237
- package/docs/guides/many-to-many.md +0 -929
- package/docs/guides/subscriptions.md +0 -537
- package/docs/reference/api.md +0 -1373
- package/docs/reference/client.md +0 -224
- package/src/client/stores/index.js +0 -8
- package/src/client/stores/useAppStore.js +0 -285
- package/src/client/stores/useWsStore.js +0 -289
- package/src/client/ws.js +0 -762
- package/src/compiler/cli/compile-example.js +0 -33
- package/src/compiler/cli/compile-subscribable.js +0 -43
- package/src/compiler/cli/debug-compile.js +0 -44
- package/src/compiler/cli/debug-parse.js +0 -26
- package/src/compiler/cli/debug-path-parser.js +0 -18
- package/src/compiler/cli/debug-subscribable-parser.js +0 -21
- package/src/compiler/cli/index.js +0 -174
- package/src/compiler/codegen/auth-codegen.js +0 -153
- package/src/compiler/codegen/drop-semantics-codegen.js +0 -553
- package/src/compiler/codegen/graph-rules-codegen.js +0 -450
- package/src/compiler/codegen/notification-codegen.js +0 -232
- package/src/compiler/codegen/operation-codegen.js +0 -1382
- package/src/compiler/codegen/permission-codegen.js +0 -318
- package/src/compiler/codegen/subscribable-codegen.js +0 -827
- package/src/compiler/compiler.js +0 -371
- package/src/compiler/index.js +0 -11
- package/src/compiler/parser/entity-parser.js +0 -440
- package/src/compiler/parser/path-parser.js +0 -290
- package/src/compiler/parser/subscribable-parser.js +0 -244
- package/src/database/dzql-core.sql +0 -161
- package/src/database/migrations/001_schema.sql +0 -60
- package/src/database/migrations/002_functions.sql +0 -890
- package/src/database/migrations/003_operations.sql +0 -1135
- package/src/database/migrations/004_search.sql +0 -581
- package/src/database/migrations/005_entities.sql +0 -730
- package/src/database/migrations/006_auth.sql +0 -94
- package/src/database/migrations/007_events.sql +0 -133
- package/src/database/migrations/008_hello.sql +0 -18
- package/src/database/migrations/008a_meta.sql +0 -172
- package/src/database/migrations/009_subscriptions.sql +0 -240
- package/src/database/migrations/010_atomic_updates.sql +0 -157
- package/src/database/migrations/010_fix_m2m_events.sql +0 -94
- package/src/index.js +0 -40
- package/src/server/api.js +0 -9
- package/src/server/db.js +0 -442
- package/src/server/index.js +0 -317
- package/src/server/logger.js +0 -259
- package/src/server/mcp.js +0 -594
- package/src/server/meta-route.js +0 -251
- package/src/server/namespace.js +0 -292
- package/src/server/subscriptions.js +0 -351
- package/src/server/ws.js +0 -573
|
@@ -1,554 +0,0 @@
|
|
|
1
|
-
# Drop Semantics
|
|
2
|
-
|
|
3
|
-
Compile-time manifest describing valid drag-and-drop interactions for canvas UIs.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
When you compile entity definitions, DZQL generates a `drop-semantics.json` file that describes all valid drag-and-drop relationships between entities. This allows canvas UIs to:
|
|
8
|
-
|
|
9
|
-
- Know which entities can be dropped onto which targets
|
|
10
|
-
- Display appropriate visual feedback (containment, frames, edges, badges)
|
|
11
|
-
- Execute the correct database operation for each drop
|
|
12
|
-
- Provide unlink/remove functionality
|
|
13
|
-
|
|
14
|
-
**Key benefit:** The canvas never interprets SQL - it reads a static manifest and knows exactly what connections are valid.
|
|
15
|
-
|
|
16
|
-
## Quick Start
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
dzql compile entities/domain.sql -o compiled/
|
|
20
|
-
# Outputs:
|
|
21
|
-
# compiled/entities.sql
|
|
22
|
-
# compiled/drop-semantics.json ← Canvas consumes this
|
|
23
|
-
# compiled/checksums.json
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Output Format
|
|
27
|
-
|
|
28
|
-
```json
|
|
29
|
-
{
|
|
30
|
-
"entities": {
|
|
31
|
-
"tasks": {
|
|
32
|
-
"droppable_on": {
|
|
33
|
-
"task_groups": [{
|
|
34
|
-
"relation": "group_id",
|
|
35
|
-
"type": "fk",
|
|
36
|
-
"action": "move",
|
|
37
|
-
"visual": "containment",
|
|
38
|
-
"label": "Move to group",
|
|
39
|
-
"operation": {
|
|
40
|
-
"method": "save",
|
|
41
|
-
"entity": "tasks",
|
|
42
|
-
"params": { "id": "@source.id", "group_id": "@target.id" }
|
|
43
|
-
},
|
|
44
|
-
"removable": true,
|
|
45
|
-
"remove_operation": {
|
|
46
|
-
"method": "save",
|
|
47
|
-
"entity": "tasks",
|
|
48
|
-
"params": { "id": "@source.id", "group_id": null }
|
|
49
|
-
}
|
|
50
|
-
}],
|
|
51
|
-
"users": [{
|
|
52
|
-
"relation": "assigned_to_user_id",
|
|
53
|
-
"type": "fk",
|
|
54
|
-
"action": "move",
|
|
55
|
-
"visual": "badge",
|
|
56
|
-
"label": "Move to user",
|
|
57
|
-
"primary_direction": "accepts",
|
|
58
|
-
"operation": { ... }
|
|
59
|
-
}]
|
|
60
|
-
},
|
|
61
|
-
"accepts": {
|
|
62
|
-
"users": [{
|
|
63
|
-
"relation": "assigned_to_user_id",
|
|
64
|
-
"type": "fk",
|
|
65
|
-
"action": "assign",
|
|
66
|
-
"visual": "badge",
|
|
67
|
-
"label": "Assign user",
|
|
68
|
-
"operation": {
|
|
69
|
-
"method": "save",
|
|
70
|
-
"entity": "tasks",
|
|
71
|
-
"params": { "id": "@target.id", "assigned_to_user_id": "@source.id" }
|
|
72
|
-
},
|
|
73
|
-
"removable": true,
|
|
74
|
-
"remove_operation": {
|
|
75
|
-
"method": "save",
|
|
76
|
-
"entity": "tasks",
|
|
77
|
-
"params": { "id": "@target.id", "assigned_to_user_id": null }
|
|
78
|
-
}
|
|
79
|
-
}]
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### Primary Direction Hint
|
|
87
|
-
|
|
88
|
-
Some relationships have a natural gesture direction. For example, you typically drop a *user* onto a *task* to assign them, not the other way around. When `primary_direction: "accepts"` is present, the canvas should prioritize the `accepts` entry for UI affordances (drop zones, visual hints).
|
|
89
|
-
|
|
90
|
-
```json
|
|
91
|
-
{
|
|
92
|
-
"relation": "assigned_to_user_id",
|
|
93
|
-
"primary_direction": "accepts"
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
The compiler infers this from naming patterns like `assigned_to_*`, `created_by_*`, `author`, `owner`, etc.
|
|
98
|
-
|
|
99
|
-
## Terminology
|
|
100
|
-
|
|
101
|
-
- **source** - The entity being dragged
|
|
102
|
-
- **target** - The entity being dropped onto
|
|
103
|
-
- **droppable_on** - What THIS entity can be dropped onto
|
|
104
|
-
- **accepts** - What can be dropped onto THIS entity
|
|
105
|
-
|
|
106
|
-
## Derivation Rules
|
|
107
|
-
|
|
108
|
-
The compiler derives drop semantics from your schema relationships:
|
|
109
|
-
|
|
110
|
-
### 1. Foreign Key Relationships
|
|
111
|
-
|
|
112
|
-
```sql
|
|
113
|
-
-- tasks.group_id REFERENCES task_groups
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
Generates:
|
|
117
|
-
|
|
118
|
-
| Perspective | Entry | Meaning |
|
|
119
|
-
|-------------|-------|---------|
|
|
120
|
-
| `tasks.droppable_on.task_groups` | action: "move" | Drag task onto group → update task.group_id |
|
|
121
|
-
| `tasks.accepts.task_groups` | action: "assign" | Drag group onto task → update task.group_id |
|
|
122
|
-
|
|
123
|
-
### 2. Many-to-Many (Junction Tables)
|
|
124
|
-
|
|
125
|
-
```sql
|
|
126
|
-
-- post_tags(post_id, tag_id)
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
Generates:
|
|
130
|
-
|
|
131
|
-
| Perspective | Entry | Meaning |
|
|
132
|
-
|-------------|-------|---------|
|
|
133
|
-
| `posts.droppable_on.tags` | action: "link" | Drag post onto tag → insert junction |
|
|
134
|
-
| `posts.accepts.tags` | action: "link" | Drag tag onto post → insert junction |
|
|
135
|
-
|
|
136
|
-
### 3. Self-Referential FK
|
|
137
|
-
|
|
138
|
-
```sql
|
|
139
|
-
-- categories.parent_id REFERENCES categories
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
Generates:
|
|
143
|
-
|
|
144
|
-
| Perspective | Entry | Meaning |
|
|
145
|
-
|-------------|-------|---------|
|
|
146
|
-
| `categories.droppable_on.categories` | action: "reparent" | Drag category onto another → set parent |
|
|
147
|
-
|
|
148
|
-
### 4. Self-Referential Junction (Dependencies)
|
|
149
|
-
|
|
150
|
-
```sql
|
|
151
|
-
-- task_dependencies(task_id, depends_on_task_id)
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
Generates:
|
|
155
|
-
|
|
156
|
-
| Perspective | Entry | Meaning |
|
|
157
|
-
|-------------|-------|---------|
|
|
158
|
-
| `tasks.droppable_on.tasks` | action: "link", visual: "edge" | Drag task onto task → create dependency edge |
|
|
159
|
-
|
|
160
|
-
## Visual Types
|
|
161
|
-
|
|
162
|
-
The `visual` field tells the canvas how to render each relationship:
|
|
163
|
-
|
|
164
|
-
| Visual | Meaning | When Used |
|
|
165
|
-
|--------|---------|-----------|
|
|
166
|
-
| `containment` | Node moves inside container | Tree structures (folders, groups) |
|
|
167
|
-
| `frame` | Visual bounding box around members | Sets, collections |
|
|
168
|
-
| `edge` | Arrow drawn between nodes | Dependencies, relationships |
|
|
169
|
-
| `badge` | Tag/chip displayed on node | Assignments, references |
|
|
170
|
-
|
|
171
|
-
### Automatic Visual Inference
|
|
172
|
-
|
|
173
|
-
The compiler infers visual type using these rules (in order):
|
|
174
|
-
|
|
175
|
-
1. **Self-referential junction** → `edge`
|
|
176
|
-
2. **Self-referential FK** → `containment`
|
|
177
|
-
3. **Target has self-referential FK** (is a tree) → `containment`
|
|
178
|
-
4. **Name ends with `_groups`, `_folders`, `_categories`** → `containment`
|
|
179
|
-
5. **Name ends with `_sets`, `_collections`, `_lists`** → `frame`
|
|
180
|
-
6. **Default** → `badge`
|
|
181
|
-
|
|
182
|
-
### Edge Direction
|
|
183
|
-
|
|
184
|
-
For `edge` visuals (self-referential junctions), the output includes direction:
|
|
185
|
-
|
|
186
|
-
```json
|
|
187
|
-
{
|
|
188
|
-
"visual": "edge",
|
|
189
|
-
"direction": "source_to_target",
|
|
190
|
-
"self_referential": true
|
|
191
|
-
}
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
The canvas can use this to draw arrows in the correct direction.
|
|
195
|
-
|
|
196
|
-
## Remove Operations
|
|
197
|
-
|
|
198
|
-
Every relationship includes remove semantics:
|
|
199
|
-
|
|
200
|
-
### FK Relationships
|
|
201
|
-
|
|
202
|
-
```json
|
|
203
|
-
{
|
|
204
|
-
"removable": true,
|
|
205
|
-
"remove_operation": {
|
|
206
|
-
"method": "save",
|
|
207
|
-
"entity": "tasks",
|
|
208
|
-
"params": { "id": "@source.id", "group_id": null }
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
Setting the FK to `null` unlinks the relationship.
|
|
214
|
-
|
|
215
|
-
### Junction Relationships
|
|
216
|
-
|
|
217
|
-
```json
|
|
218
|
-
{
|
|
219
|
-
"removable": true,
|
|
220
|
-
"remove_operation": {
|
|
221
|
-
"method": "delete",
|
|
222
|
-
"entity": "post_tags",
|
|
223
|
-
"params": { "post_id": "@source.id", "tag_id": "@target.id" }
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
Deleting the junction record removes the link.
|
|
229
|
-
|
|
230
|
-
## Composite Primary Keys
|
|
231
|
-
|
|
232
|
-
Entities with composite primary keys include all key fields in params:
|
|
233
|
-
|
|
234
|
-
```json
|
|
235
|
-
{
|
|
236
|
-
"operation": {
|
|
237
|
-
"method": "save",
|
|
238
|
-
"entity": "org_items",
|
|
239
|
-
"params": {
|
|
240
|
-
"org_id": "@source.org_id",
|
|
241
|
-
"item_code": "@source.item_code",
|
|
242
|
-
"category_id": "@target.id"
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
## Canvas Integration
|
|
249
|
-
|
|
250
|
-
### Checking Valid Drops
|
|
251
|
-
|
|
252
|
-
```javascript
|
|
253
|
-
function canDrop(sourceEntity, sourceId, targetEntity, targetId) {
|
|
254
|
-
const semantics = dropSemantics.entities[sourceEntity];
|
|
255
|
-
if (!semantics) return false;
|
|
256
|
-
|
|
257
|
-
return semantics.droppable_on[targetEntity]?.length > 0;
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
### Executing Drop
|
|
262
|
-
|
|
263
|
-
```javascript
|
|
264
|
-
async function executeDrop(ws, sourceEntity, sourceData, targetEntity, targetData, relationIndex = 0) {
|
|
265
|
-
const action = dropSemantics.entities[sourceEntity].droppable_on[targetEntity][relationIndex];
|
|
266
|
-
|
|
267
|
-
const params = resolveParams(action.operation.params, sourceData, targetData);
|
|
268
|
-
|
|
269
|
-
if (action.operation.method === 'save') {
|
|
270
|
-
await ws.api.save[action.operation.entity](params);
|
|
271
|
-
} else if (action.operation.method === 'delete') {
|
|
272
|
-
await ws.api.delete[action.operation.entity](params);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function resolveParams(template, sourceData, targetData) {
|
|
277
|
-
const params = {};
|
|
278
|
-
for (const [key, value] of Object.entries(template)) {
|
|
279
|
-
if (typeof value === 'string' && value.startsWith('@source.')) {
|
|
280
|
-
params[key] = sourceData[value.replace('@source.', '')];
|
|
281
|
-
} else if (typeof value === 'string' && value.startsWith('@target.')) {
|
|
282
|
-
params[key] = targetData[value.replace('@target.', '')];
|
|
283
|
-
} else {
|
|
284
|
-
params[key] = value;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
return params;
|
|
288
|
-
}
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
### Getting Visual Hint
|
|
292
|
-
|
|
293
|
-
```javascript
|
|
294
|
-
function getDropVisual(sourceEntity, targetEntity) {
|
|
295
|
-
const action = dropSemantics.entities[sourceEntity]?.droppable_on[targetEntity]?.[0];
|
|
296
|
-
return action?.visual || null;
|
|
297
|
-
}
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
### Multiple Relations Picker
|
|
301
|
-
|
|
302
|
-
When multiple relations exist between the same entities (e.g., task→task could be "depends on" or "blocks"), show a picker:
|
|
303
|
-
|
|
304
|
-
```javascript
|
|
305
|
-
function getDropOptions(sourceEntity, targetEntity) {
|
|
306
|
-
const actions = dropSemantics.entities[sourceEntity]?.droppable_on[targetEntity] || [];
|
|
307
|
-
return actions.map((action, index) => ({
|
|
308
|
-
index,
|
|
309
|
-
label: action.label,
|
|
310
|
-
visual: action.visual,
|
|
311
|
-
relation: action.relation
|
|
312
|
-
}));
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// In Vue component
|
|
316
|
-
<template>
|
|
317
|
-
<div v-if="dropOptions.length > 1" class="relation-picker">
|
|
318
|
-
<button
|
|
319
|
-
v-for="option in dropOptions"
|
|
320
|
-
:key="option.index"
|
|
321
|
-
@click="executeDrop(option.index)"
|
|
322
|
-
>
|
|
323
|
-
{{ option.label }}
|
|
324
|
-
</button>
|
|
325
|
-
</div>
|
|
326
|
-
</template>
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
## Example: Complete Task Management
|
|
330
|
-
|
|
331
|
-
### Schema
|
|
332
|
-
|
|
333
|
-
```sql
|
|
334
|
-
-- Groups with hierarchy
|
|
335
|
-
CREATE TABLE task_groups (
|
|
336
|
-
id SERIAL PRIMARY KEY,
|
|
337
|
-
name TEXT NOT NULL,
|
|
338
|
-
parent_id INT REFERENCES task_groups(id)
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
-- Tasks
|
|
342
|
-
CREATE TABLE tasks (
|
|
343
|
-
id SERIAL PRIMARY KEY,
|
|
344
|
-
title TEXT NOT NULL,
|
|
345
|
-
group_id INT REFERENCES task_groups(id),
|
|
346
|
-
assigned_to_user_id INT REFERENCES users(id)
|
|
347
|
-
);
|
|
348
|
-
|
|
349
|
-
-- Task sets (for batch operations)
|
|
350
|
-
CREATE TABLE task_sets (
|
|
351
|
-
id SERIAL PRIMARY KEY,
|
|
352
|
-
name TEXT NOT NULL
|
|
353
|
-
);
|
|
354
|
-
|
|
355
|
-
CREATE TABLE task_set_members (
|
|
356
|
-
task_id INT REFERENCES tasks(id) ON DELETE CASCADE,
|
|
357
|
-
set_id INT REFERENCES task_sets(id) ON DELETE CASCADE,
|
|
358
|
-
PRIMARY KEY (task_id, set_id)
|
|
359
|
-
);
|
|
360
|
-
|
|
361
|
-
-- Task dependencies
|
|
362
|
-
CREATE TABLE task_dependencies (
|
|
363
|
-
task_id INT REFERENCES tasks(id) ON DELETE CASCADE,
|
|
364
|
-
depends_on_task_id INT REFERENCES tasks(id) ON DELETE CASCADE,
|
|
365
|
-
PRIMARY KEY (task_id, depends_on_task_id)
|
|
366
|
-
);
|
|
367
|
-
|
|
368
|
-
-- Entity registrations
|
|
369
|
-
SELECT dzql.register_entity('task_groups', 'name', ARRAY['name'],
|
|
370
|
-
jsonb_build_object('parent', 'task_groups'),
|
|
371
|
-
false, '{}', '{}',
|
|
372
|
-
jsonb_build_object('view', ARRAY[]::text[], 'create', ARRAY[]::text[],
|
|
373
|
-
'update', ARRAY[]::text[], 'delete', ARRAY[]::text[])
|
|
374
|
-
);
|
|
375
|
-
|
|
376
|
-
SELECT dzql.register_entity('tasks', 'title', ARRAY['title'],
|
|
377
|
-
jsonb_build_object('group', 'task_groups', 'assigned_to_user', 'users'),
|
|
378
|
-
false, '{}', '{}',
|
|
379
|
-
jsonb_build_object('view', ARRAY[]::text[], 'create', ARRAY[]::text[],
|
|
380
|
-
'update', ARRAY[]::text[], 'delete', ARRAY[]::text[]),
|
|
381
|
-
jsonb_build_object(
|
|
382
|
-
'many_to_many', jsonb_build_object(
|
|
383
|
-
'sets', jsonb_build_object(
|
|
384
|
-
'junction_table', 'task_set_members',
|
|
385
|
-
'local_key', 'task_id',
|
|
386
|
-
'foreign_key', 'set_id',
|
|
387
|
-
'target_entity', 'task_sets',
|
|
388
|
-
'id_field', 'set_ids'
|
|
389
|
-
),
|
|
390
|
-
'dependencies', jsonb_build_object(
|
|
391
|
-
'junction_table', 'task_dependencies',
|
|
392
|
-
'local_key', 'task_id',
|
|
393
|
-
'foreign_key', 'depends_on_task_id',
|
|
394
|
-
'target_entity', 'tasks',
|
|
395
|
-
'id_field', 'dependency_ids'
|
|
396
|
-
)
|
|
397
|
-
)
|
|
398
|
-
)
|
|
399
|
-
);
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
### Generated Drop Semantics
|
|
403
|
-
|
|
404
|
-
```json
|
|
405
|
-
{
|
|
406
|
-
"entities": {
|
|
407
|
-
"task_groups": {
|
|
408
|
-
"droppable_on": {
|
|
409
|
-
"task_groups": [{
|
|
410
|
-
"relation": "parent_id",
|
|
411
|
-
"type": "fk",
|
|
412
|
-
"action": "reparent",
|
|
413
|
-
"visual": "containment",
|
|
414
|
-
"label": "Set parent",
|
|
415
|
-
"operation": { ... },
|
|
416
|
-
"removable": true,
|
|
417
|
-
"remove_operation": { ... }
|
|
418
|
-
}]
|
|
419
|
-
},
|
|
420
|
-
"accepts": {
|
|
421
|
-
"tasks": [{ ... }]
|
|
422
|
-
}
|
|
423
|
-
},
|
|
424
|
-
"tasks": {
|
|
425
|
-
"droppable_on": {
|
|
426
|
-
"task_groups": [{
|
|
427
|
-
"relation": "group_id",
|
|
428
|
-
"type": "fk",
|
|
429
|
-
"action": "move",
|
|
430
|
-
"visual": "containment",
|
|
431
|
-
"label": "Move to group",
|
|
432
|
-
"operation": { ... }
|
|
433
|
-
}],
|
|
434
|
-
"users": [{
|
|
435
|
-
"relation": "assigned_to_user_id",
|
|
436
|
-
"type": "fk",
|
|
437
|
-
"action": "move",
|
|
438
|
-
"visual": "badge",
|
|
439
|
-
"label": "Move to assigned to user",
|
|
440
|
-
"operation": { ... }
|
|
441
|
-
}],
|
|
442
|
-
"task_sets": [{
|
|
443
|
-
"relation": "task_set_members",
|
|
444
|
-
"type": "junction",
|
|
445
|
-
"action": "link",
|
|
446
|
-
"visual": "frame",
|
|
447
|
-
"label": "Add task set member",
|
|
448
|
-
"operation": { ... }
|
|
449
|
-
}],
|
|
450
|
-
"tasks": [{
|
|
451
|
-
"relation": "task_dependencies",
|
|
452
|
-
"type": "junction",
|
|
453
|
-
"action": "link",
|
|
454
|
-
"visual": "edge",
|
|
455
|
-
"direction": "source_to_target",
|
|
456
|
-
"label": "Add task dependency",
|
|
457
|
-
"operation": { ... },
|
|
458
|
-
"self_referential": true
|
|
459
|
-
}]
|
|
460
|
-
},
|
|
461
|
-
"accepts": {
|
|
462
|
-
"users": [{
|
|
463
|
-
"relation": "assigned_to_user_id",
|
|
464
|
-
"type": "fk",
|
|
465
|
-
"action": "assign",
|
|
466
|
-
"visual": "badge",
|
|
467
|
-
"label": "Assign assigned to user",
|
|
468
|
-
"operation": { ... }
|
|
469
|
-
}],
|
|
470
|
-
"task_sets": [{ ... }],
|
|
471
|
-
"tasks": [{
|
|
472
|
-
"visual": "edge",
|
|
473
|
-
"direction": "target_to_source"
|
|
474
|
-
}]
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
```
|
|
480
|
-
|
|
481
|
-
### Canvas Interpretation
|
|
482
|
-
|
|
483
|
-
| Drop | Visual | Result |
|
|
484
|
-
|------|--------|--------|
|
|
485
|
-
| Task → Group | containment | Task node moves inside group container |
|
|
486
|
-
| Task → Task | edge | Arrow drawn from source to target |
|
|
487
|
-
| User → Task | badge | User chip appears on task node |
|
|
488
|
-
| Task → Set | frame | Task included in set's visual boundary |
|
|
489
|
-
| Group → Group | containment | Group nests inside another group |
|
|
490
|
-
|
|
491
|
-
## Relationship Types Summary
|
|
492
|
-
|
|
493
|
-
| Schema Pattern | Type | Action | Default Visual |
|
|
494
|
-
|----------------|------|--------|----------------|
|
|
495
|
-
| `A.fk_id REFERENCES B` | `fk` | `move` | `badge` or `containment`* |
|
|
496
|
-
| `A.fk_id REFERENCES A` | `fk` | `reparent` | `containment` |
|
|
497
|
-
| Junction(A, B) | `junction` | `link` | `badge` or `frame`* |
|
|
498
|
-
| Junction(A, A) | `junction` | `link` | `edge` |
|
|
499
|
-
|
|
500
|
-
*Visual depends on target entity name and structure
|
|
501
|
-
|
|
502
|
-
## Canvas Position Storage
|
|
503
|
-
|
|
504
|
-
The drop-semantics manifest covers relationships but not node positions. For canvas x/y coordinates, consider:
|
|
505
|
-
|
|
506
|
-
### Option A: JSON Column on Entity
|
|
507
|
-
|
|
508
|
-
Simple approach for single-user or shared layouts:
|
|
509
|
-
|
|
510
|
-
```sql
|
|
511
|
-
ALTER TABLE tasks ADD COLUMN canvas JSONB DEFAULT '{}';
|
|
512
|
-
|
|
513
|
-
-- Store position
|
|
514
|
-
UPDATE tasks SET canvas = jsonb_build_object('x', 100, 'y', 200) WHERE id = 1;
|
|
515
|
-
|
|
516
|
-
-- Or include in entity definition for automatic handling
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
### Option B: Separate Positions Table
|
|
520
|
-
|
|
521
|
-
For multi-user layouts or per-project views:
|
|
522
|
-
|
|
523
|
-
```sql
|
|
524
|
-
CREATE TABLE canvas_positions (
|
|
525
|
-
entity TEXT NOT NULL,
|
|
526
|
-
record_id INT NOT NULL,
|
|
527
|
-
user_id INT REFERENCES users(id),
|
|
528
|
-
project_id INT, -- Optional: per-project layouts
|
|
529
|
-
x FLOAT NOT NULL,
|
|
530
|
-
y FLOAT NOT NULL,
|
|
531
|
-
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
532
|
-
PRIMARY KEY (entity, record_id, COALESCE(user_id, 0), COALESCE(project_id, 0))
|
|
533
|
-
);
|
|
534
|
-
|
|
535
|
-
CREATE INDEX idx_canvas_positions_lookup
|
|
536
|
-
ON canvas_positions(entity, user_id, project_id);
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
### Option C: Client-Side Storage
|
|
540
|
-
|
|
541
|
-
For personal layouts that don't need server persistence:
|
|
542
|
-
|
|
543
|
-
```javascript
|
|
544
|
-
// localStorage per user
|
|
545
|
-
const positions = JSON.parse(localStorage.getItem('canvas_positions') || '{}');
|
|
546
|
-
positions[`${entity}:${id}`] = { x, y };
|
|
547
|
-
localStorage.setItem('canvas_positions', JSON.stringify(positions));
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
## See Also
|
|
551
|
-
|
|
552
|
-
- [Many-to-Many](./many-to-many.md) - Junction table configuration
|
|
553
|
-
- [Compiler Guide](../compiler/README.md) - Full compilation workflow
|
|
554
|
-
- [Custom Functions](./custom-functions.md) - Extending with business logic
|