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,299 +0,0 @@
1
- # Atomic Updates for Subscribables
2
-
3
- Atomic updates enable efficient real-time synchronization by sending only the changes (insert/update/delete) to subscribed clients, instead of re-querying and sending the entire document on every change.
4
-
5
- ## Overview
6
-
7
- ### Problem Solved
8
-
9
- Previously, when any data changed that affected a subscription, the server would:
10
- 1. Re-query the entire document using `get_<subscribable>()`
11
- 2. Send the complete document to the client
12
- 3. Client replaces its entire local state
13
-
14
- This approach has several problems:
15
- - **Network inefficiency**: Sends full product catalogue when one task template duration changes
16
- - **Database load**: Re-executes complex queries on every tiny change
17
- - **Client state loss**: Replaces entire local state, losing UI state (scroll position, expanded rows, etc.)
18
-
19
- ### Solution: Atomic Updates
20
-
21
- With atomic updates, the server:
22
- 1. Forwards the raw event (table, operation, primary key, data) directly to clients
23
- 2. Client applies the patch to their local copy of the document
24
- 3. Only changed data traverses the network
25
-
26
- Benefits:
27
- - **Efficient**: O(change size) instead of O(document size) per update
28
- - **Preserved state**: Client UI state remains intact
29
- - **Reduced database load**: No re-querying on every change
30
-
31
- ## How It Works
32
-
33
- ### 1. Subscribe: Initial Data + Schema
34
-
35
- When a client subscribes, they receive:
36
- - The full initial document (unchanged)
37
- - A **schema** that maps table names to document paths
38
-
39
- ```javascript
40
- const { data, schema, unsubscribe } = await ws.api.subscribe_venue_detail(
41
- { venue_id: 1 },
42
- (updated) => console.log('Updated:', updated)
43
- );
44
-
45
- // schema = {
46
- // root: 'venues',
47
- // paths: {
48
- // 'venues': '.', // Root entity
49
- // 'organisations': 'org', // FK expansion
50
- // 'sites': 'sites', // Child collection
51
- // 'packages': 'packages' // Child collection
52
- // }
53
- // }
54
- ```
55
-
56
- ### 2. On Changes: Atomic Events
57
-
58
- When data changes, instead of re-querying, the server sends a `subscription:event` message:
59
-
60
- ```json
61
- {
62
- "jsonrpc": "2.0",
63
- "method": "subscription:event",
64
- "params": {
65
- "subscription_id": "550e8400-e29b-41d4-a716-446655440000",
66
- "subscribable": "venue_detail",
67
- "event": {
68
- "table": "sites",
69
- "op": "update",
70
- "pk": { "id": 5 },
71
- "data": { "id": 5, "name": "Updated Site Name", "venue_id": 1 },
72
- "before": { "id": 5, "name": "Old Name", "venue_id": 1 }
73
- }
74
- }
75
- }
76
- ```
77
-
78
- ### 3. Client: Apply Patch
79
-
80
- The client uses the schema to locate where the change belongs in the document and applies it:
81
-
82
- - **insert**: Adds new item to the appropriate array
83
- - **update**: Finds item by primary key and merges changes
84
- - **delete**: Finds item by primary key and removes it
85
-
86
- The callback receives the updated local document, preserving any UI state.
87
-
88
- ## Scope Tables
89
-
90
- Each subscribable tracks which tables are "in scope" - tables that can affect the document. This enables an optimization: events from tables not in scope are immediately skipped, avoiding unnecessary `_affected_documents()` calls.
91
-
92
- ### Interpreted Mode (Runtime)
93
-
94
- In interpreted mode, scope tables are stored in the `dzql.subscribables` table:
95
-
96
- ```sql
97
- SELECT scope_tables FROM dzql.subscribables WHERE name = 'venue_detail';
98
- -- Returns: ['venues', 'organisations', 'sites', 'packages']
99
- ```
100
-
101
- Scope tables are automatically extracted when registering a subscribable:
102
-
103
- ```sql
104
- SELECT dzql.register_subscribable(
105
- 'venue_detail',
106
- '{"subscribe": [...]}'::jsonb,
107
- '{"venue_id": "int"}'::jsonb,
108
- 'venues', -- root_entity -> scope includes 'venues'
109
- '{
110
- "org": "organisations", -- scope includes 'organisations'
111
- "sites": {"entity": "sites", ...} -- scope includes 'sites'
112
- }'::jsonb
113
- );
114
-
115
- -- scope_tables automatically set to: ['venues', 'organisations', 'sites']
116
- ```
117
-
118
- ### Compiled Mode (Static SQL)
119
-
120
- In compiled mode, scope tables are embedded directly in the `get_<name>()` function return value. No `dzql.subscribables` table is required.
121
-
122
- The compiled function returns:
123
- ```json
124
- {
125
- "data": { ... },
126
- "schema": {
127
- "root": "organisations",
128
- "paths": {
129
- "organisations": ".",
130
- "products": "products"
131
- },
132
- "scopeTables": ["organisations", "products"]
133
- }
134
- }
135
- ```
136
-
137
- When a client subscribes:
138
- 1. Server calls `get_<name>(params, user_id)`
139
- 2. Server extracts the embedded schema (including `scopeTables`)
140
- 3. Server caches the metadata for event filtering
141
- 4. Client receives `data` and `schema` for client-side patching
142
-
143
- **Important**: The scope tables are only cached after the first subscribe call. This means:
144
- - The `dzql.subscribables` table is not needed for compiled deployments
145
- - Event filtering works correctly once at least one client has subscribed
146
- - If no clients have subscribed, events will still be processed (the scope check is skipped when cache is empty)
147
-
148
- ## Path Mapping
149
-
150
- The path mapping tells the client where each table's data lives in the document structure:
151
-
152
- | Table | Path | Meaning |
153
- |-------|------|---------|
154
- | `venues` | `.` | Root level |
155
- | `organisations` | `org` | `document.org` |
156
- | `sites` | `sites` | `document.sites[]` |
157
- | `allocations` | `packages.allocations` | `document.packages[].allocations[]` |
158
-
159
- ### Nested Relations
160
-
161
- For nested relations, paths are dot-separated:
162
-
163
- ```javascript
164
- relations = {
165
- packages: {
166
- entity: 'packages',
167
- filter: 'venue_id=$venue_id',
168
- include: {
169
- allocations: 'allocations'
170
- }
171
- }
172
- }
173
-
174
- // Results in paths:
175
- // 'packages' -> 'packages'
176
- // 'allocations' -> 'packages.allocations'
177
- ```
178
-
179
- ## Client-Side Patching
180
-
181
- The `WebSocketManager` automatically handles `subscription:event` messages:
182
-
183
- ```javascript
184
- // Internally, when subscription:event is received:
185
- applyAtomicUpdate(sub, event) {
186
- const { table, op, pk, data } = event;
187
- const path = sub.schema.paths[table];
188
-
189
- if (path === '.') {
190
- // Root entity update
191
- Object.assign(sub.localData[sub.schema.root], data);
192
- } else {
193
- // Relation update
194
- const arr = getArrayAtPath(sub.localData, path);
195
- if (op === 'insert') arr.push(data);
196
- if (op === 'update') {
197
- const idx = arr.findIndex(item => pkMatch(item, pk));
198
- if (idx !== -1) Object.assign(arr[idx], data);
199
- }
200
- if (op === 'delete') {
201
- const idx = arr.findIndex(item => pkMatch(item, pk));
202
- if (idx !== -1) arr.splice(idx, 1);
203
- }
204
- }
205
-
206
- // Trigger callback with patched document
207
- sub.callback(sub.localData);
208
- }
209
- ```
210
-
211
- ## Composite Primary Keys
212
-
213
- Atomic updates support composite primary keys:
214
-
215
- ```json
216
- {
217
- "pk": { "product_id": 1, "part_id": 2 }
218
- }
219
- ```
220
-
221
- The client matches all key fields when finding items to update or delete.
222
-
223
- ## Migration from Full Re-queries
224
-
225
- If you have existing subscribables, atomic updates are enabled automatically when:
226
- 1. The `scope_tables` column is populated (happens on `register_subscribable`)
227
- 2. The client receives the `schema` in the subscribe response
228
-
229
- No code changes required - the system is backward compatible.
230
-
231
- ## Debugging
232
-
233
- ### Check Schema (Compiled Mode)
234
-
235
- Subscribe and log the full schema including scopeTables:
236
-
237
- ```javascript
238
- const { data, schema } = await ws.api.subscribe_product_catalogue(
239
- { organisation_id: 1 },
240
- () => {}
241
- );
242
- console.log('Schema:', schema);
243
- // Expected: { root: 'organisations', paths: {...}, scopeTables: [...] }
244
- ```
245
-
246
- If `schema.root` is null or `schema.scopeTables` is missing, ensure you're using dzql >= 0.5.10 and have recompiled your subscribable functions.
247
-
248
- ### Check Scope Tables (Interpreted Mode)
249
-
250
- ```sql
251
- SELECT name, scope_tables
252
- FROM dzql.subscribables
253
- WHERE name = 'your_subscribable';
254
- ```
255
-
256
- ### Verify Path Mapping
257
-
258
- ```javascript
259
- const { schema } = await ws.api.subscribe_venue_detail(
260
- { venue_id: 1 },
261
- () => {}
262
- );
263
- console.log('Path mapping:', schema.paths);
264
- // Should map each table to its document path
265
- ```
266
-
267
- ### Server Logs
268
-
269
- Enable debug logging to see atomic events:
270
-
271
- ```bash
272
- LOG_CATEGORIES="notify:debug" bun run server
273
- ```
274
-
275
- You should see:
276
- ```
277
- Cached compiled metadata for product_catalogue: { scopeTables: ['organisations', 'products'] }
278
- product_catalogue: 1 param set(s) affected by products:update
279
- Sent atomic event to subscription abc123... (products:update)
280
- ```
281
-
282
- ### Common Issues
283
-
284
- 1. **`schema.root` is null**: Update to dzql >= 0.5.10 and recompile your subscribables
285
- 2. **No updates pushed**: Check that scopeTables includes the changed table
286
- 3. **Metadata not cached**: Ensure at least one client has subscribed before events occur
287
-
288
- ## Limitations
289
-
290
- 1. **Deeply nested updates**: For very deep nesting (3+ levels), paths become complex. Consider flattening your subscribable structure.
291
-
292
- 2. **Cascading deletes**: When a parent is deleted, the client may receive the parent delete before child deletes. The client handles missing parents gracefully.
293
-
294
- 3. **Permission changes**: If a user loses access mid-subscription, they may receive one final event before the subscription is terminated.
295
-
296
- ## See Also
297
-
298
- - [Subscriptions Guide](./subscriptions.md) - Full subscribable documentation
299
- - [Getting Started with Subscriptions](../getting-started/subscriptions-quick-start.md)