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.
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
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env bun
2
+ import { loadDomain } from "./compiler/loader.js";
3
+ import { analyzeDomain } from "./compiler/analyzer.js";
4
+ import { generateIR } from "./compiler/ir.js";
5
+ import { generateCoreSQL, generateEntitySQL, generateSchemaSQL } from "./codegen/sql.js";
6
+ import { generateSubscribableSQL } from "./codegen/subscribable_sql.js";
7
+ import { generateManifest } from "./codegen/manifest.js";
8
+ import { generateSubscribableStore } from "./codegen/subscribable_store.js";
9
+ import { generateClientSDK } from "./codegen/client.js";
10
+ import { writeFileSync, mkdirSync, copyFileSync, rmSync } from "fs";
11
+ import { resolve, dirname } from "path";
12
+
13
+ const args = process.argv.slice(2);
14
+ const command = args[0];
15
+ const input = args[1];
16
+ let outputDir = "dist"; // Default output directory
17
+
18
+ // Parse optional output directory flag
19
+ const outputFlagIndex = args.indexOf('-o');
20
+ const longOutputFlagIndex = args.indexOf('--output');
21
+
22
+ if (outputFlagIndex > -1 && args[outputFlagIndex + 1]) {
23
+ outputDir = args[outputFlagIndex + 1];
24
+ } else if (longOutputFlagIndex > -1 && args[longOutputFlagIndex + 1]) {
25
+ outputDir = args[longOutputFlagIndex + 1];
26
+ }
27
+
28
+ async function main() {
29
+ console.log("TZQL Compiler v0.0.1");
30
+
31
+ if (command === "compile") {
32
+ if (!input) {
33
+ console.error("Usage: tzql compile <file>");
34
+ process.exit(1);
35
+ }
36
+
37
+ try {
38
+ // Phase 1: Load & Analyze
39
+ const fullInputPath = resolve(process.cwd(), input);
40
+
41
+ // Clean Output Directory
42
+ const absOutputDir = resolve(process.cwd(), outputDir);
43
+ console.log(`[Compiler] Cleaning ${absOutputDir}...`);
44
+ try {
45
+ rmSync(absOutputDir, { recursive: true, force: true });
46
+ } catch (e) { /* ignore */ }
47
+
48
+ const domain = await loadDomain(fullInputPath);
49
+ console.log("[Compiler] Domain loaded.");
50
+
51
+ const errors = analyzeDomain(domain);
52
+ if (errors.length > 0) {
53
+ console.error("[Compiler] Validation Failed:");
54
+ errors.forEach(err => console.error(` - ${err}`));
55
+ process.exit(1);
56
+ }
57
+
58
+ // Phase 2: Generate IR
59
+ const ir = generateIR(domain);
60
+ console.log(`[Compiler] IR Generated. Subscribables: ${Object.keys(ir.subscribables).join(', ')}`);
61
+
62
+ // Phase 3: Generate SQL
63
+ const coreSQL = generateCoreSQL();
64
+ const entitySQL: string[] = [];
65
+ for (const [name, entityIR] of Object.entries(ir.entities)) {
66
+ entitySQL.push(generateSchemaSQL(name, entityIR));
67
+ // Skip CRUD generation for unmanaged entities (e.g., junction tables)
68
+ if (entityIR.managed !== false) {
69
+ entitySQL.push(generateEntitySQL(name, entityIR));
70
+ } else {
71
+ console.log(`[Compiler] Skipping CRUD for unmanaged entity: ${name}`);
72
+ }
73
+ }
74
+
75
+ // Generate subscribable SQL functions
76
+ const subscribableSQL: string[] = [];
77
+ for (const [name, subIR] of Object.entries(ir.subscribables)) {
78
+ console.log(`[Compiler] Generating SQL for subscribable: ${name}`);
79
+ subscribableSQL.push(generateSubscribableSQL(name, subIR as any, ir.entities));
80
+ }
81
+
82
+ // Collect custom functions SQL
83
+ const customFunctionSQL: string[] = [];
84
+ for (const fn of ir.customFunctions) {
85
+ console.log(`[Compiler] Adding custom function: ${fn.name}`);
86
+ customFunctionSQL.push(fn.sql);
87
+ }
88
+
89
+ // Phase 4: Generate Manifest
90
+ const manifest = generateManifest(ir);
91
+ console.log(`[Compiler] Manifest functions: ${Object.keys(manifest.functions).length}`);
92
+ if (!manifest.functions['subscribe_venue_detail']) {
93
+ console.error("[Compiler] ERROR: subscribe_venue_detail missing from manifest functions!");
94
+ }
95
+
96
+ // --- OUTPUT GENERATION ---
97
+
98
+ // 1. Database
99
+ const dbDir = resolve(outputDir, "db/migrations");
100
+ mkdirSync(dbDir, { recursive: true });
101
+
102
+ writeFileSync(resolve(dbDir, `000_core.sql`), coreSQL);
103
+ const timestamp = new Date().toISOString().replace(/[:.-]/g, '');
104
+
105
+ // Combine entity SQL with custom functions
106
+ const schemaContent = customFunctionSQL.length > 0
107
+ ? entitySQL.join('\n') + '\n\n-- Custom Functions\n' + customFunctionSQL.join('\n\n')
108
+ : entitySQL.join('\n');
109
+ writeFileSync(resolve(dbDir, `${timestamp}_schema.sql`), schemaContent);
110
+
111
+ if (customFunctionSQL.length > 0) {
112
+ console.log(`[Generated] ${customFunctionSQL.length} Custom Functions`);
113
+ }
114
+
115
+ // Write subscribable SQL
116
+ if (subscribableSQL.length > 0) {
117
+ writeFileSync(resolve(dbDir, `${timestamp}_subscribables.sql`), subscribableSQL.join('\n\n'));
118
+ console.log(`[Generated] ${subscribableSQL.length} Subscribable SQL functions`);
119
+ }
120
+
121
+ console.log(`[Generated] DB Migrations in ${dbDir}`);
122
+
123
+ // 2. Runtime
124
+ const runtimeDir = resolve(outputDir, "runtime");
125
+ mkdirSync(runtimeDir, { recursive: true });
126
+ writeFileSync(resolve(runtimeDir, `manifest.json`), JSON.stringify(manifest, null, 2));
127
+ console.log(`[Generated] Runtime Manifest in ${runtimeDir}`);
128
+
129
+ // 3. Client SDK (TypeScript)
130
+ const clientDir = resolve(outputDir, "client");
131
+ mkdirSync(clientDir, { recursive: true });
132
+
133
+ // Generate Core SDK as TypeScript
134
+ const clientCode = generateClientSDK(manifest);
135
+ writeFileSync(resolve(clientDir, `ws.ts`), clientCode);
136
+
137
+ // Generate Index
138
+ writeFileSync(resolve(clientDir, `index.ts`), `export * from './ws.js';`);
139
+
140
+ console.log(`[Generated] Client SDK in ${clientDir}`);
141
+
142
+ // 4. Stores (TypeScript)
143
+ const storeDir = resolve(clientDir, "stores");
144
+ mkdirSync(storeDir, { recursive: true });
145
+
146
+ for (const subName of Object.keys(ir.subscribables)) {
147
+ const storeCode = generateSubscribableStore(manifest, subName);
148
+ const fileName = `use${subName.replace(/(^|_)([a-z])/g, (g) => g.at(-1)!.toUpperCase())}Store.ts`;
149
+ writeFileSync(resolve(storeDir, fileName), storeCode);
150
+ }
151
+ console.log(`[Generated] ${Object.keys(ir.subscribables).length} Pinia Stores in ${storeDir}`);
152
+
153
+ console.log("[Compiler] Build Complete.");
154
+
155
+ } catch (e) {
156
+ console.error(e);
157
+ process.exit(1);
158
+ }
159
+ } else {
160
+ console.log("Unknown command. Try 'compile'.");
161
+ }
162
+ }
163
+
164
+ main();
@@ -0,0 +1 @@
1
+ export * from './ws.js';
@@ -0,0 +1,286 @@
1
+ // Core WebSocket Manager for TZQL Client
2
+ // Handles connection, auth, reconnects, and message dispatching.
3
+ // This is a pure transport layer - it does not manage or cache data.
4
+
5
+ export interface WebSocketOptions {
6
+ url?: string;
7
+ maxReconnectAttempts?: number;
8
+ tokenName?: string;
9
+ }
10
+
11
+ // Get default token name from environment (build-time injection)
12
+ function getDefaultTokenName(): string {
13
+ // Vite: import.meta.env.VITE_TZQL_TOKEN_NAME
14
+ // @ts-ignore
15
+ if (typeof import.meta !== 'undefined' && import.meta.env?.VITE_TZQL_TOKEN_NAME) {
16
+ // @ts-ignore
17
+ return import.meta.env.VITE_TZQL_TOKEN_NAME;
18
+ }
19
+ // Node/bundlers: process.env.TZQL_TOKEN_NAME
20
+ if (typeof process !== 'undefined' && process.env?.TZQL_TOKEN_NAME) {
21
+ return process.env.TZQL_TOKEN_NAME;
22
+ }
23
+ return 'tzql_token';
24
+ }
25
+
26
+ export class WebSocketManager {
27
+ protected ws: WebSocket | null = null;
28
+ protected messageId = 0;
29
+ protected pendingRequests = new Map<number, { resolve: (val: any) => void; reject: (err: any) => void }>();
30
+ protected methodHandlers = new Map<string, Set<(params: any) => void>>();
31
+ protected subscriptionCallbacks = new Map<string, (event: any) => void>();
32
+ protected readyCallbacks = new Set<(user: any) => void>();
33
+ protected reconnectAttempts = 0;
34
+ protected maxReconnectAttempts = 5;
35
+ protected tokenName = 'tzql_token';
36
+ protected isShuttingDown = false;
37
+
38
+ // Connection state
39
+ public user: any = null;
40
+ public ready: boolean = false;
41
+
42
+ // To be populated by generated code
43
+ public api: any = {};
44
+
45
+ constructor(options: WebSocketOptions = {}) {
46
+ this.maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
47
+ this.tokenName = options.tokenName ?? getDefaultTokenName();
48
+ }
49
+
50
+ async login(credentials: any) {
51
+ try {
52
+ const result = await this.call('login_user', credentials);
53
+ if (result && result.token) {
54
+ if (typeof localStorage !== 'undefined') {
55
+ localStorage.setItem(this.tokenName, result.token);
56
+ }
57
+ await this.authenticate(result.token);
58
+ }
59
+ return result;
60
+ } catch (e) {
61
+ throw e;
62
+ }
63
+ }
64
+
65
+ async authenticate(token: string) {
66
+ return this.call('auth', { token });
67
+ }
68
+
69
+ async register(credentials: any, options: any = {}) {
70
+ try {
71
+ const params = { ...credentials, options };
72
+ const result = await this.call('register_user', params);
73
+ if (result && result.token) {
74
+ if (typeof localStorage !== 'undefined') {
75
+ localStorage.setItem(this.tokenName, result.token);
76
+ }
77
+ }
78
+ return result;
79
+ } catch (e) {
80
+ throw e;
81
+ }
82
+ }
83
+
84
+ async logout() {
85
+ if (typeof localStorage !== 'undefined') {
86
+ localStorage.removeItem(this.tokenName);
87
+ }
88
+ this.user = null;
89
+ this.ready = false;
90
+ try { await this.call('logout'); } catch(e) {}
91
+ this.ws?.close();
92
+ }
93
+
94
+ connect(url: string | null = null, timeout = 5000): Promise<void> {
95
+ return new Promise((resolve, reject) => {
96
+ this.ready = false;
97
+ this.user = null;
98
+
99
+ let wsUrl = url;
100
+ if (!wsUrl) {
101
+ if (typeof window !== "undefined") {
102
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
103
+ wsUrl = protocol + "//" + window.location.host + "/ws";
104
+ } else {
105
+ wsUrl = "ws://localhost:3000/ws";
106
+ }
107
+ }
108
+
109
+ if (typeof localStorage !== 'undefined') {
110
+ const token = localStorage.getItem(this.tokenName);
111
+ if (token) {
112
+ if (wsUrl.includes('?')) wsUrl += '&token=' + encodeURIComponent(token);
113
+ else wsUrl += '?token=' + encodeURIComponent(token);
114
+ }
115
+ }
116
+
117
+ const connectionTimeout = setTimeout(() => {
118
+ if (this.ws) this.ws.close();
119
+ reject(new Error('WebSocket connection timed out after ' + timeout + 'ms'));
120
+ }, timeout);
121
+
122
+ this.ws = new WebSocket(wsUrl);
123
+
124
+ this.ws.onopen = () => {
125
+ clearTimeout(connectionTimeout);
126
+ console.log('[TZQL] Connected to ' + wsUrl);
127
+ this.reconnectAttempts = 0;
128
+ resolve();
129
+ };
130
+
131
+ this.ws.onmessage = (event) => {
132
+ try {
133
+ const message = JSON.parse(event.data);
134
+ this.handleMessage(message);
135
+ } catch (error) {
136
+ console.error("[TZQL] Failed to parse message:", error);
137
+ }
138
+ };
139
+
140
+ this.ws.onclose = () => {
141
+ console.log("[TZQL] Disconnected");
142
+ if (!this.isShuttingDown) {
143
+ this.attemptReconnect();
144
+ }
145
+ };
146
+
147
+ this.ws.onerror = (error) => {
148
+ clearTimeout(connectionTimeout);
149
+ console.error("[TZQL] Connection error:", error);
150
+ reject(error);
151
+ };
152
+ });
153
+ }
154
+
155
+ attemptReconnect() {
156
+ if (this.reconnectAttempts < this.maxReconnectAttempts) {
157
+ this.reconnectAttempts++;
158
+ const delay = 1000 * this.reconnectAttempts;
159
+ setTimeout(() => {
160
+ console.log('[TZQL] Reconnecting (' + this.reconnectAttempts + ')...');
161
+ this.connect();
162
+ }, delay);
163
+ }
164
+ }
165
+
166
+ handleMessage(message: any) {
167
+ // Handle connection:ready message
168
+ if (message.method === "connection:ready") {
169
+ this.user = message.params?.user || null;
170
+ this.ready = true;
171
+ this.readyCallbacks.forEach((cb) => cb(this.user));
172
+ return;
173
+ }
174
+
175
+ // Handle RPC responses (messages with id)
176
+ if (message.id && this.pendingRequests.has(message.id)) {
177
+ const resolver = this.pendingRequests.get(message.id);
178
+ if (resolver) {
179
+ this.pendingRequests.delete(message.id);
180
+ if (message.error) {
181
+ const err: any = new Error(message.error.message || 'Unknown error');
182
+ err.code = message.error.code;
183
+ resolver.reject(err);
184
+ } else {
185
+ resolver.resolve(message.result);
186
+ }
187
+ }
188
+ return;
189
+ }
190
+
191
+ // Handle subscription events - dispatch to registered subscription callbacks
192
+ if (message.method === "subscription:event") {
193
+ const event = message.params?.event;
194
+ if (event) {
195
+ // Dispatch to all subscription handlers - they filter by table/scope
196
+ for (const [subId, callback] of this.subscriptionCallbacks) {
197
+ callback(event);
198
+ }
199
+ }
200
+ return;
201
+ }
202
+
203
+ // Handle other server-initiated messages (broadcasts) - route to registered handlers
204
+ if (message.method) {
205
+ const handlers = this.methodHandlers.get(message.method);
206
+ if (handlers) {
207
+ handlers.forEach((cb) => cb(message.params));
208
+ }
209
+ }
210
+ }
211
+
212
+ call(method: string, params: any = {}) {
213
+ return new Promise((resolve, reject) => {
214
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
215
+ reject(new Error("WebSocket not connected"));
216
+ return;
217
+ }
218
+ const id = ++this.messageId;
219
+ this.pendingRequests.set(id, { resolve, reject });
220
+ this.ws.send(JSON.stringify({ jsonrpc: "2.0", method, params, id }));
221
+ });
222
+ }
223
+
224
+ /**
225
+ * Register a callback for a server-initiated method
226
+ * @param method - The method name to listen for
227
+ * @param callback - Called with params when server sends this method
228
+ * @returns Unsubscribe function
229
+ */
230
+ on(method: string, callback: (params: any) => void) {
231
+ if (!this.methodHandlers.has(method)) {
232
+ this.methodHandlers.set(method, new Set());
233
+ }
234
+ this.methodHandlers.get(method)!.add(callback);
235
+ return () => {
236
+ const handlers = this.methodHandlers.get(method);
237
+ if (handlers) {
238
+ handlers.delete(callback);
239
+ if (handlers.size === 0) {
240
+ this.methodHandlers.delete(method);
241
+ }
242
+ }
243
+ };
244
+ }
245
+
246
+ /**
247
+ * Register a callback to be called when connection is ready
248
+ * @param callback - Called with user profile (or null if not authenticated)
249
+ * @returns Unsubscribe function
250
+ */
251
+ onReady(callback: (user: any) => void) {
252
+ if (this.ready) {
253
+ callback(this.user);
254
+ }
255
+ this.readyCallbacks.add(callback);
256
+ return () => this.readyCallbacks.delete(callback);
257
+ }
258
+
259
+ /**
260
+ * Subscribe to a subscribable document
261
+ * @param method - The subscribe method name (e.g., "subscribe_venue_detail")
262
+ * @param params - Subscription parameters
263
+ * @param callback - Called with initial data and on updates
264
+ * @returns Promise that resolves to an unsubscribe function
265
+ */
266
+ async subscribe(method: string, params: any, callback: (data: any) => void): Promise<() => void> {
267
+ // Call server to get initial snapshot and subscription_id
268
+ const result = await this.call(method, params) as {
269
+ subscription_id: string;
270
+ data: any;
271
+ };
272
+
273
+ // Register callback for subscription events
274
+ this.subscriptionCallbacks.set(result.subscription_id, callback);
275
+
276
+ // Call callback with initial data
277
+ callback(result.data);
278
+
279
+ // Return unsubscribe function
280
+ return () => {
281
+ this.subscriptionCallbacks.delete(result.subscription_id);
282
+ // Notify server
283
+ this.call(`unsubscribe_${method.replace('subscribe_', '')}`, { subscription_id: result.subscription_id }).catch(() => {});
284
+ };
285
+ }
286
+ }
@@ -0,0 +1,8 @@
1
+ # Database connection
2
+ DATABASE_URL=postgres://{{name}}:{{name}}@localhost:5432/{{name}}
3
+
4
+ # JWT Secret (change in production!)
5
+ JWT_SECRET=dev-secret-change-in-production
6
+
7
+ # Server port
8
+ PORT=3000
@@ -0,0 +1,101 @@
1
+ # {{name}}
2
+
3
+ A real-time database application powered by [DZQL](https://github.com/blueshed/dzql).
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Start PostgreSQL
9
+ bun run db:up
10
+
11
+ # Copy environment file
12
+ cp .env.example .env
13
+
14
+ # Compile domain and generate SQL
15
+ bun run compile
16
+
17
+ # Run migrations
18
+ bun run db:migrate
19
+
20
+ # Start development server
21
+ bun run dev
22
+ ```
23
+
24
+ ## Project Structure
25
+
26
+ ```
27
+ ├── domain.ts # Entity definitions and subscriptions
28
+ ├── server.ts # Runtime server configuration
29
+ ├── compose.yml # Docker Compose for PostgreSQL
30
+ ├── dist/ # Generated output (after compile)
31
+ │ ├── db/ # SQL migrations
32
+ │ ├── runtime/ # Server manifest
33
+ │ └── client/ # TypeScript SDK & Pinia stores
34
+ └── .env # Environment variables
35
+ ```
36
+
37
+ ## Domain Definition
38
+
39
+ Edit `domain.ts` to define your entities:
40
+
41
+ ```typescript
42
+ export const entities = {
43
+ posts: {
44
+ schema: {
45
+ id: 'serial PRIMARY KEY',
46
+ title: 'text NOT NULL',
47
+ content: 'text'
48
+ },
49
+ permissions: {
50
+ view: [],
51
+ create: [],
52
+ update: ['@author_id'],
53
+ delete: ['@author_id']
54
+ }
55
+ }
56
+ };
57
+ ```
58
+
59
+ ## API Endpoints
60
+
61
+ After starting the server:
62
+
63
+ - **WebSocket**: `ws://localhost:3000/ws`
64
+ - **Health**: `GET http://localhost:3000/health`
65
+
66
+ ## Client Usage
67
+
68
+ ```typescript
69
+ import { TzqlClient } from './dist/client';
70
+
71
+ const client = new TzqlClient('ws://localhost:3000/ws');
72
+
73
+ // Register
74
+ await client.api.register_user({
75
+ email: 'user@example.com',
76
+ password: 'secret'
77
+ });
78
+
79
+ // Login
80
+ const { token } = await client.api.login_user({
81
+ email: 'user@example.com',
82
+ password: 'secret'
83
+ });
84
+
85
+ // CRUD operations
86
+ const post = await client.api.save_posts({
87
+ title: 'Hello World'
88
+ });
89
+
90
+ // Real-time subscription
91
+ await client.api.subscribe_post_detail(
92
+ { post_id: post.id },
93
+ (data) => console.log('Updated:', data)
94
+ );
95
+ ```
96
+
97
+ ## Learn More
98
+
99
+ - [DZQL Documentation](https://github.com/blueshed/dzql)
100
+ - [Graph Rules](https://github.com/blueshed/dzql/docs/guides/graph-rules.md)
101
+ - [Subscriptions](https://github.com/blueshed/dzql/docs/guides/subscriptions.md)
@@ -0,0 +1,14 @@
1
+ services:
2
+ postgres:
3
+ image: postgres:16
4
+ environment:
5
+ POSTGRES_USER: {{name}}
6
+ POSTGRES_PASSWORD: {{name}}
7
+ POSTGRES_DB: {{name}}
8
+ ports:
9
+ - "5432:5432"
10
+ volumes:
11
+ - pgdata:/var/lib/postgresql/data
12
+
13
+ volumes:
14
+ pgdata: