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
package/.env.sample ADDED
@@ -0,0 +1,28 @@
1
+ # TZQL Environment Variables
2
+ # Copy this file to .env and modify as needed
3
+
4
+ # ===================
5
+ # Server (Runtime)
6
+ # ===================
7
+
8
+ # PostgreSQL connection string
9
+ DATABASE_URL=postgres://user:password@localhost:5432/database
10
+
11
+ # Server port
12
+ PORT=3000
13
+
14
+ # Path to compiled manifest (relative to working directory)
15
+ MANIFEST_PATH=./dist/runtime/manifest.json
16
+
17
+ # JWT secret for authentication (CHANGE IN PRODUCTION!)
18
+ JWT_SECRET=your-secure-secret-key-here
19
+
20
+ # ===================
21
+ # Client (Browser)
22
+ # ===================
23
+ # These are injected at build time by your bundler (Vite, Webpack, etc.)
24
+
25
+ # Token name for localStorage (Vite: prefix with VITE_)
26
+ # VITE_TZQL_TOKEN_NAME=myapp_token
27
+ # Or for other bundlers:
28
+ # TZQL_TOKEN_NAME=myapp_token
package/compose.yml ADDED
@@ -0,0 +1,28 @@
1
+ services:
2
+ postgres:
3
+ image: postgres:16-alpine
4
+ container_name: tzql-test-db
5
+ environment:
6
+ POSTGRES_USER: dzql_test
7
+ POSTGRES_PASSWORD: dzql_test
8
+ POSTGRES_DB: dzql_test
9
+ ports:
10
+ - "5433:5432" # Map container's 5432 to host's 5433
11
+ volumes:
12
+ - tzql-test-data:/var/lib/postgresql/data
13
+ healthcheck:
14
+ test: ["CMD-SHELL", "pg_isready -U dzql_test -d dzql_test"]
15
+ interval: 5s
16
+ timeout: 5s
17
+ retries: 5
18
+ command:
19
+ - "postgres"
20
+ - "-c"
21
+ - "log_statement=all"
22
+ - "-c"
23
+ - "log_destination=stderr"
24
+ - "-c"
25
+ - "logging_collector=off"
26
+
27
+ volumes:
28
+ tzql-test-data:
@@ -0,0 +1 @@
1
+ export * from './ws.js';
@@ -0,0 +1,114 @@
1
+ // Generated by TZQL Compiler v2.0.0
2
+ // Do not edit this file directly.
3
+
4
+ import { defineStore } from 'pinia';
5
+ import { ref, type Ref } from 'vue';
6
+ import { ws } from '../index.js';
7
+
8
+ /** Parameters for my_profile subscription */
9
+ export interface MyProfileParams {
10
+ [key: string]: unknown;
11
+ }
12
+
13
+ /** Event from server for patching */
14
+ export interface PatchEvent {
15
+ table: string;
16
+ op: 'insert' | 'update' | 'delete';
17
+ pk: { id: number };
18
+ data: Record<string, unknown> | null;
19
+ }
20
+
21
+ /** Document wrapper with loading state */
22
+ export interface DocumentWrapper<T> {
23
+ data: T;
24
+ loading: boolean;
25
+ ready: Promise<void>;
26
+ }
27
+
28
+ export const useMyProfileStore = defineStore('sub-my_profile', () => {
29
+ const documents: Ref<Record<string, DocumentWrapper<Record<string, unknown>>>> = ref({});
30
+ const unsubscribers = new Map<string, () => void>();
31
+
32
+ async function bind(params: MyProfileParams): Promise<DocumentWrapper<Record<string, unknown>>> {
33
+ const key = JSON.stringify(params);
34
+
35
+ if (documents.value[key]) {
36
+ const existing = documents.value[key];
37
+ if (existing.loading) {
38
+ await existing.ready;
39
+ }
40
+ return existing;
41
+ }
42
+
43
+ let resolveReady!: () => void;
44
+ const ready = new Promise<void>((resolve) => { resolveReady = resolve; });
45
+
46
+ documents.value[key] = { data: {}, loading: true, ready };
47
+ let isFirst = true;
48
+
49
+ const unsubscribe = await ws.api.subscribe_my_profile(params, (eventData: unknown) => {
50
+ if (isFirst) {
51
+ // Initial data - merge into existing object to preserve reactivity
52
+ Object.assign(documents.value[key].data, eventData as Record<string, unknown>);
53
+ documents.value[key].loading = false;
54
+ isFirst = false;
55
+ resolveReady();
56
+ } else {
57
+ // Patch event
58
+ applyPatch(documents.value[key].data, eventData as PatchEvent);
59
+ }
60
+ });
61
+
62
+ unsubscribers.set(key, unsubscribe as () => void);
63
+ await ready;
64
+ return documents.value[key];
65
+ }
66
+
67
+ function unbind(params: MyProfileParams): void {
68
+ const key = JSON.stringify(params);
69
+ const unsubscribe = unsubscribers.get(key);
70
+ if (unsubscribe) {
71
+ unsubscribe();
72
+ unsubscribers.delete(key);
73
+ delete documents.value[key];
74
+ }
75
+ }
76
+
77
+ function applyPatch(doc: Record<string, unknown>, event: PatchEvent): void {
78
+ if (!doc) return;
79
+ switch (event.table) {
80
+ case 'users':
81
+ if (event.op === 'update' && doc.users) {
82
+ Object.assign(doc.users, event.data);
83
+ }
84
+ break;
85
+ case 'acts_for':
86
+ handleArrayPatch(doc.memberships, event);
87
+ break;
88
+ case 'organisations':
89
+ if (event.data && event.data.membership_id) {
90
+ const parent = doc.memberships?.find((p: { id: number }) => p.id === event.data.membership_id);
91
+ if (parent && parent.org) {
92
+ handleArrayPatch(parent.org, event);
93
+ }
94
+ }
95
+ break;
96
+ }
97
+ }
98
+
99
+ function handleArrayPatch(arr: unknown[] | undefined, event: PatchEvent): void {
100
+ if (!arr || !Array.isArray(arr)) return;
101
+ const pkValue = event.pk?.id;
102
+ const idx = arr.findIndex((i: unknown) => (i as { id: number }).id === pkValue);
103
+
104
+ if (event.op === 'insert') {
105
+ if (idx === -1 && event.data) arr.push(event.data);
106
+ } else if (event.op === 'update') {
107
+ if (idx !== -1 && event.data) Object.assign(arr[idx] as object, event.data);
108
+ } else if (event.op === 'delete') {
109
+ if (idx !== -1) arr.splice(idx, 1);
110
+ }
111
+ }
112
+
113
+ return { bind, unbind, documents };
114
+ });
@@ -0,0 +1,131 @@
1
+ // Generated by TZQL Compiler v2.0.0
2
+ // Do not edit this file directly.
3
+
4
+ import { defineStore } from 'pinia';
5
+ import { ref, type Ref } from 'vue';
6
+ import { ws } from '../index.js';
7
+
8
+ /** Parameters for org_dashboard subscription */
9
+ export interface OrgDashboardParams {
10
+ org_id: number;
11
+ }
12
+
13
+ /** Event from server for patching */
14
+ export interface PatchEvent {
15
+ table: string;
16
+ op: 'insert' | 'update' | 'delete';
17
+ pk: { id: number };
18
+ data: Record<string, unknown> | null;
19
+ }
20
+
21
+ /** Document wrapper with loading state */
22
+ export interface DocumentWrapper<T> {
23
+ data: T;
24
+ loading: boolean;
25
+ ready: Promise<void>;
26
+ }
27
+
28
+ export const useOrgDashboardStore = defineStore('sub-org_dashboard', () => {
29
+ const documents: Ref<Record<string, DocumentWrapper<Record<string, unknown>>>> = ref({});
30
+ const unsubscribers = new Map<string, () => void>();
31
+
32
+ async function bind(params: OrgDashboardParams): Promise<DocumentWrapper<Record<string, unknown>>> {
33
+ const key = JSON.stringify(params);
34
+
35
+ if (documents.value[key]) {
36
+ const existing = documents.value[key];
37
+ if (existing.loading) {
38
+ await existing.ready;
39
+ }
40
+ return existing;
41
+ }
42
+
43
+ let resolveReady!: () => void;
44
+ const ready = new Promise<void>((resolve) => { resolveReady = resolve; });
45
+
46
+ documents.value[key] = { data: {}, loading: true, ready };
47
+ let isFirst = true;
48
+
49
+ const unsubscribe = await ws.api.subscribe_org_dashboard(params, (eventData: unknown) => {
50
+ if (isFirst) {
51
+ // Initial data - merge into existing object to preserve reactivity
52
+ Object.assign(documents.value[key].data, eventData as Record<string, unknown>);
53
+ documents.value[key].loading = false;
54
+ isFirst = false;
55
+ resolveReady();
56
+ } else {
57
+ // Patch event
58
+ applyPatch(documents.value[key].data, eventData as PatchEvent);
59
+ }
60
+ });
61
+
62
+ unsubscribers.set(key, unsubscribe as () => void);
63
+ await ready;
64
+ return documents.value[key];
65
+ }
66
+
67
+ function unbind(params: OrgDashboardParams): void {
68
+ const key = JSON.stringify(params);
69
+ const unsubscribe = unsubscribers.get(key);
70
+ if (unsubscribe) {
71
+ unsubscribe();
72
+ unsubscribers.delete(key);
73
+ delete documents.value[key];
74
+ }
75
+ }
76
+
77
+ function applyPatch(doc: Record<string, unknown>, event: PatchEvent): void {
78
+ if (!doc) return;
79
+ switch (event.table) {
80
+ case 'organisations':
81
+ if (event.op === 'update' && doc.organisations) {
82
+ Object.assign(doc.organisations, event.data);
83
+ }
84
+ break;
85
+ case 'venues':
86
+ handleArrayPatch(doc.venues, event);
87
+ break;
88
+ case 'sites':
89
+ if (event.data && event.data.venue_id) {
90
+ const parent = doc.venues?.find((p: { id: number }) => p.id === event.data.venue_id);
91
+ if (parent && parent.sites) {
92
+ handleArrayPatch(parent.sites, event);
93
+ }
94
+ }
95
+ break;
96
+ case 'products':
97
+ handleArrayPatch(doc.products, event);
98
+ break;
99
+ case 'packages':
100
+ handleArrayPatch(doc.packages, event);
101
+ break;
102
+ case 'brands':
103
+ handleArrayPatch(doc.brands, event);
104
+ break;
105
+ case 'artwork':
106
+ if (event.data && event.data.brand_id) {
107
+ const parent = doc.brands?.find((p: { id: number }) => p.id === event.data.brand_id);
108
+ if (parent && parent.artwork) {
109
+ handleArrayPatch(parent.artwork, event);
110
+ }
111
+ }
112
+ break;
113
+ }
114
+ }
115
+
116
+ function handleArrayPatch(arr: unknown[] | undefined, event: PatchEvent): void {
117
+ if (!arr || !Array.isArray(arr)) return;
118
+ const pkValue = event.pk?.id;
119
+ const idx = arr.findIndex((i: unknown) => (i as { id: number }).id === pkValue);
120
+
121
+ if (event.op === 'insert') {
122
+ if (idx === -1 && event.data) arr.push(event.data);
123
+ } else if (event.op === 'update') {
124
+ if (idx !== -1 && event.data) Object.assign(arr[idx] as object, event.data);
125
+ } else if (event.op === 'delete') {
126
+ if (idx !== -1) arr.splice(idx, 1);
127
+ }
128
+ }
129
+
130
+ return { bind, unbind, documents };
131
+ });
@@ -0,0 +1,117 @@
1
+ // Generated by TZQL Compiler v2.0.0
2
+ // Do not edit this file directly.
3
+
4
+ import { defineStore } from 'pinia';
5
+ import { ref, type Ref } from 'vue';
6
+ import { ws } from '../index.js';
7
+
8
+ /** Parameters for venue_detail subscription */
9
+ export interface VenueDetailParams {
10
+ venue_id: number;
11
+ }
12
+
13
+ /** Event from server for patching */
14
+ export interface PatchEvent {
15
+ table: string;
16
+ op: 'insert' | 'update' | 'delete';
17
+ pk: { id: number };
18
+ data: Record<string, unknown> | null;
19
+ }
20
+
21
+ /** Document wrapper with loading state */
22
+ export interface DocumentWrapper<T> {
23
+ data: T;
24
+ loading: boolean;
25
+ ready: Promise<void>;
26
+ }
27
+
28
+ export const useVenueDetailStore = defineStore('sub-venue_detail', () => {
29
+ const documents: Ref<Record<string, DocumentWrapper<Record<string, unknown>>>> = ref({});
30
+ const unsubscribers = new Map<string, () => void>();
31
+
32
+ async function bind(params: VenueDetailParams): Promise<DocumentWrapper<Record<string, unknown>>> {
33
+ const key = JSON.stringify(params);
34
+
35
+ if (documents.value[key]) {
36
+ const existing = documents.value[key];
37
+ if (existing.loading) {
38
+ await existing.ready;
39
+ }
40
+ return existing;
41
+ }
42
+
43
+ let resolveReady!: () => void;
44
+ const ready = new Promise<void>((resolve) => { resolveReady = resolve; });
45
+
46
+ documents.value[key] = { data: {}, loading: true, ready };
47
+ let isFirst = true;
48
+
49
+ const unsubscribe = await ws.api.subscribe_venue_detail(params, (eventData: unknown) => {
50
+ if (isFirst) {
51
+ // Initial data - merge into existing object to preserve reactivity
52
+ Object.assign(documents.value[key].data, eventData as Record<string, unknown>);
53
+ documents.value[key].loading = false;
54
+ isFirst = false;
55
+ resolveReady();
56
+ } else {
57
+ // Patch event
58
+ applyPatch(documents.value[key].data, eventData as PatchEvent);
59
+ }
60
+ });
61
+
62
+ unsubscribers.set(key, unsubscribe as () => void);
63
+ await ready;
64
+ return documents.value[key];
65
+ }
66
+
67
+ function unbind(params: VenueDetailParams): void {
68
+ const key = JSON.stringify(params);
69
+ const unsubscribe = unsubscribers.get(key);
70
+ if (unsubscribe) {
71
+ unsubscribe();
72
+ unsubscribers.delete(key);
73
+ delete documents.value[key];
74
+ }
75
+ }
76
+
77
+ function applyPatch(doc: Record<string, unknown>, event: PatchEvent): void {
78
+ if (!doc) return;
79
+ switch (event.table) {
80
+ case 'venues':
81
+ if (event.op === 'update' && doc.venues) {
82
+ Object.assign(doc.venues, event.data);
83
+ }
84
+ break;
85
+ case 'organisations':
86
+ handleArrayPatch(doc.org, event);
87
+ break;
88
+ case 'sites':
89
+ handleArrayPatch(doc.sites, event);
90
+ break;
91
+ case 'allocations':
92
+ if (event.data && event.data.site_id) {
93
+ const parent = doc.sites?.find((p: { id: number }) => p.id === event.data.site_id);
94
+ if (parent && parent.allocations) {
95
+ handleArrayPatch(parent.allocations, event);
96
+ }
97
+ }
98
+ break;
99
+ }
100
+ }
101
+
102
+ function handleArrayPatch(arr: unknown[] | undefined, event: PatchEvent): void {
103
+ if (!arr || !Array.isArray(arr)) return;
104
+ const pkValue = event.pk?.id;
105
+ const idx = arr.findIndex((i: unknown) => (i as { id: number }).id === pkValue);
106
+
107
+ if (event.op === 'insert') {
108
+ if (idx === -1 && event.data) arr.push(event.data);
109
+ } else if (event.op === 'update') {
110
+ if (idx !== -1 && event.data) Object.assign(arr[idx] as object, event.data);
111
+ } else if (event.op === 'delete') {
112
+ if (idx !== -1) arr.splice(idx, 1);
113
+ }
114
+ }
115
+
116
+ return { bind, unbind, documents };
117
+ });