dzql 0.5.33 → 0.6.1

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 (142) 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 +309 -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 +653 -0
  20. package/docs/project-setup.md +456 -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 +166 -0
  39. package/src/client/index.ts +1 -0
  40. package/src/client/ws.ts +286 -0
  41. package/src/runtime/auth.ts +39 -0
  42. package/src/runtime/db.ts +33 -0
  43. package/src/runtime/errors.ts +51 -0
  44. package/src/runtime/index.ts +98 -0
  45. package/src/runtime/js_functions.ts +63 -0
  46. package/src/runtime/manifest_loader.ts +29 -0
  47. package/src/runtime/namespace.ts +483 -0
  48. package/src/runtime/server.ts +87 -0
  49. package/src/runtime/ws.ts +197 -0
  50. package/src/shared/ir.ts +197 -0
  51. package/tests/client.test.ts +38 -0
  52. package/tests/codegen.test.ts +71 -0
  53. package/tests/compiler.test.ts +45 -0
  54. package/tests/graph_rules.test.ts +173 -0
  55. package/tests/integration/db.test.ts +174 -0
  56. package/tests/integration/e2e.test.ts +65 -0
  57. package/tests/integration/features.test.ts +922 -0
  58. package/tests/integration/full_stack.test.ts +262 -0
  59. package/tests/integration/setup.ts +45 -0
  60. package/tests/ir.test.ts +32 -0
  61. package/tests/namespace.test.ts +395 -0
  62. package/tests/permissions.test.ts +55 -0
  63. package/tests/pinia.test.ts +48 -0
  64. package/tests/realtime.test.ts +22 -0
  65. package/tests/runtime.test.ts +80 -0
  66. package/tests/subscribable_gen.test.ts +72 -0
  67. package/tests/subscribable_reactivity.test.ts +258 -0
  68. package/tests/venues_gen.test.ts +25 -0
  69. package/tsconfig.json +20 -0
  70. package/tsconfig.tsbuildinfo +1 -0
  71. package/README.md +0 -90
  72. package/bin/cli.js +0 -727
  73. package/docs/compiler/ADVANCED_FILTERS.md +0 -183
  74. package/docs/compiler/CODING_STANDARDS.md +0 -415
  75. package/docs/compiler/COMPARISON.md +0 -673
  76. package/docs/compiler/QUICKSTART.md +0 -326
  77. package/docs/compiler/README.md +0 -134
  78. package/docs/examples/README.md +0 -38
  79. package/docs/examples/blog.sql +0 -160
  80. package/docs/examples/venue-detail-simple.sql +0 -8
  81. package/docs/examples/venue-detail-subscribable.sql +0 -45
  82. package/docs/for-ai/claude-guide.md +0 -1210
  83. package/docs/getting-started/quickstart.md +0 -125
  84. package/docs/getting-started/subscriptions-quick-start.md +0 -203
  85. package/docs/getting-started/tutorial.md +0 -1104
  86. package/docs/guides/atomic-updates.md +0 -299
  87. package/docs/guides/client-stores.md +0 -730
  88. package/docs/guides/composite-primary-keys.md +0 -158
  89. package/docs/guides/custom-functions.md +0 -362
  90. package/docs/guides/drop-semantics.md +0 -554
  91. package/docs/guides/field-defaults.md +0 -240
  92. package/docs/guides/interpreter-vs-compiler.md +0 -237
  93. package/docs/guides/many-to-many.md +0 -929
  94. package/docs/guides/subscriptions.md +0 -537
  95. package/docs/reference/api.md +0 -1373
  96. package/docs/reference/client.md +0 -224
  97. package/src/client/stores/index.js +0 -8
  98. package/src/client/stores/useAppStore.js +0 -285
  99. package/src/client/stores/useWsStore.js +0 -289
  100. package/src/client/ws.js +0 -762
  101. package/src/compiler/cli/compile-example.js +0 -33
  102. package/src/compiler/cli/compile-subscribable.js +0 -43
  103. package/src/compiler/cli/debug-compile.js +0 -44
  104. package/src/compiler/cli/debug-parse.js +0 -26
  105. package/src/compiler/cli/debug-path-parser.js +0 -18
  106. package/src/compiler/cli/debug-subscribable-parser.js +0 -21
  107. package/src/compiler/cli/index.js +0 -174
  108. package/src/compiler/codegen/auth-codegen.js +0 -153
  109. package/src/compiler/codegen/drop-semantics-codegen.js +0 -553
  110. package/src/compiler/codegen/graph-rules-codegen.js +0 -450
  111. package/src/compiler/codegen/notification-codegen.js +0 -232
  112. package/src/compiler/codegen/operation-codegen.js +0 -1382
  113. package/src/compiler/codegen/permission-codegen.js +0 -318
  114. package/src/compiler/codegen/subscribable-codegen.js +0 -827
  115. package/src/compiler/compiler.js +0 -371
  116. package/src/compiler/index.js +0 -11
  117. package/src/compiler/parser/entity-parser.js +0 -440
  118. package/src/compiler/parser/path-parser.js +0 -290
  119. package/src/compiler/parser/subscribable-parser.js +0 -244
  120. package/src/database/dzql-core.sql +0 -161
  121. package/src/database/migrations/001_schema.sql +0 -60
  122. package/src/database/migrations/002_functions.sql +0 -890
  123. package/src/database/migrations/003_operations.sql +0 -1135
  124. package/src/database/migrations/004_search.sql +0 -581
  125. package/src/database/migrations/005_entities.sql +0 -730
  126. package/src/database/migrations/006_auth.sql +0 -94
  127. package/src/database/migrations/007_events.sql +0 -133
  128. package/src/database/migrations/008_hello.sql +0 -18
  129. package/src/database/migrations/008a_meta.sql +0 -172
  130. package/src/database/migrations/009_subscriptions.sql +0 -240
  131. package/src/database/migrations/010_atomic_updates.sql +0 -157
  132. package/src/database/migrations/010_fix_m2m_events.sql +0 -94
  133. package/src/index.js +0 -40
  134. package/src/server/api.js +0 -9
  135. package/src/server/db.js +0 -442
  136. package/src/server/index.js +0 -317
  137. package/src/server/logger.js +0 -259
  138. package/src/server/mcp.js +0 -594
  139. package/src/server/meta-route.js +0 -251
  140. package/src/server/namespace.js +0 -292
  141. package/src/server/subscriptions.js +0 -351
  142. 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