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,95 @@
1
+ import { DomainIR, EntityIR, SubscribableIR } from "../../shared/ir.js";
2
+
3
+ export interface Manifest {
4
+ version: string;
5
+ functions: Record<string, FunctionDef>;
6
+ entities: Record<string, EntityIR>;
7
+ subscribables: Record<string, SubscribableIR>;
8
+ }
9
+
10
+ export interface FunctionDef {
11
+ oid?: number; // Resolved at runtime
12
+ schema: string;
13
+ name: string;
14
+ args: string[];
15
+ returnType: string;
16
+ isSubscription?: boolean; // Marks subscribe_* functions for client generation
17
+ }
18
+
19
+ export function generateManifest(ir: DomainIR): Manifest {
20
+ const functions: Record<string, FunctionDef> = {
21
+ // Built-in Auth Functions
22
+ login_user: { schema: 'dzql_v2', name: 'login_user', args: ['p_params'], returnType: 'jsonb' },
23
+ register_user: { schema: 'dzql_v2', name: 'register_user', args: ['p_params'], returnType: 'jsonb' }
24
+ };
25
+
26
+ for (const [name, entity] of Object.entries(ir.entities)) {
27
+ // Skip unmanaged entities (junction tables, etc.)
28
+ if (entity.managed === false) {
29
+ continue;
30
+ }
31
+
32
+ // Generate standard CRUD functions
33
+ const operations = ['get', 'save', 'delete', 'search', 'lookup'];
34
+
35
+ for (const op of operations) {
36
+ const funcName = `${op}_${name}`;
37
+ functions[funcName] = {
38
+ schema: 'dzql_v2',
39
+ name: funcName,
40
+ args: op === 'get' || op === 'delete'
41
+ ? ['p_user_id', 'p_pk']
42
+ : op === 'save'
43
+ ? ['p_user_id', 'p_data']
44
+ : ['p_user_id', 'p_query'],
45
+ returnType: 'jsonb'
46
+ };
47
+ }
48
+ }
49
+
50
+ for (const [subName, subIR] of Object.entries(ir.subscribables)) {
51
+ console.log(`[Manifest] Adding subscribable: ${subName}`);
52
+
53
+ // get_<name> - snapshot function
54
+ functions[`get_${subName}`] = {
55
+ schema: 'dzql_v2',
56
+ name: `get_${subName}`,
57
+ args: ['p_params', 'p_user_id'],
58
+ returnType: 'jsonb'
59
+ };
60
+
61
+ // <name>_can_subscribe - access control
62
+ functions[`${subName}_can_subscribe`] = {
63
+ schema: 'dzql_v2',
64
+ name: `${subName}_can_subscribe`,
65
+ args: ['p_user_id', 'p_params'],
66
+ returnType: 'boolean'
67
+ };
68
+
69
+ // subscribe_<name> - virtual method handled by runtime
70
+ functions[`subscribe_${subName}`] = {
71
+ schema: 'dzql_v2',
72
+ name: `subscribe_${subName}`,
73
+ args: ['p_params'],
74
+ returnType: 'jsonb',
75
+ isSubscription: true // Mark as subscription for special handling
76
+ };
77
+ }
78
+
79
+ // Add custom functions to allowlist
80
+ for (const fn of ir.customFunctions || []) {
81
+ functions[fn.name] = {
82
+ schema: 'dzql_v2',
83
+ name: fn.name,
84
+ args: fn.args || ['p_user_id', 'p_params'],
85
+ returnType: 'jsonb'
86
+ };
87
+ }
88
+
89
+ return {
90
+ version: "2.0.0",
91
+ functions,
92
+ entities: ir.entities, // Pass through for client generator
93
+ subscribables: ir.subscribables
94
+ };
95
+ }
@@ -0,0 +1,174 @@
1
+ import { Manifest } from "./manifest.js";
2
+
3
+ function toPascalCase(str: string): string {
4
+ return str.replace(/(^|_)([a-z])/g, (g) => g[g.length - 1].toUpperCase());
5
+ }
6
+
7
+ export function generatePiniaStore(manifest: Manifest, entityName: string): string {
8
+ const entity = manifest.entities[entityName];
9
+ if (!entity) {
10
+ throw new Error(`Entity '${entityName}' not found in manifest.`);
11
+ }
12
+
13
+ const pkField = entity.primaryKey[0] || 'id';
14
+ const pascalName = toPascalCase(entityName);
15
+
16
+ // Generate column types for TypeScript interface
17
+ const columnTypes = entity.columns.map(col => {
18
+ let tsType = 'unknown';
19
+ const pgType = col.type.toLowerCase();
20
+
21
+ if (pgType.includes('serial') || pgType.includes('int')) {
22
+ tsType = 'number';
23
+ } else if (pgType.includes('text') || pgType.includes('varchar') || pgType.includes('char')) {
24
+ tsType = 'string';
25
+ } else if (pgType.includes('bool')) {
26
+ tsType = 'boolean';
27
+ } else if (pgType.includes('timestamp') || pgType.includes('date')) {
28
+ tsType = 'string'; // ISO date string
29
+ } else if (pgType.includes('json')) {
30
+ tsType = 'Record<string, unknown>';
31
+ } else if (pgType.includes('decimal') || pgType.includes('numeric') || pgType.includes('float') || pgType.includes('real')) {
32
+ tsType = 'number';
33
+ }
34
+
35
+ // Handle arrays
36
+ if (col.isArray) {
37
+ tsType = `${tsType}[]`;
38
+ }
39
+
40
+ // Make optional if not primary key and not NOT NULL
41
+ const isRequired = pgType.includes('not null') || pgType.includes('primary key');
42
+ return ` ${col.name}${isRequired ? '' : '?'}: ${tsType};`;
43
+ }).join('\n');
44
+
45
+ // Primary key type
46
+ const pkCol = entity.columns.find(c => c.name === pkField);
47
+ const pkType = pkCol && (pkCol.type.toLowerCase().includes('serial') || pkCol.type.toLowerCase().includes('int'))
48
+ ? 'number'
49
+ : 'string';
50
+
51
+ return `// Generated by TZQL Compiler v${manifest.version}
52
+ // Do not edit this file directly.
53
+
54
+ import { defineStore } from 'pinia';
55
+ import { ref, type Ref } from 'vue';
56
+ import { ws } from '../../client.js';
57
+
58
+ /** ${pascalName} entity type */
59
+ export interface ${pascalName} {
60
+ ${columnTypes}
61
+ }
62
+
63
+ /** Event payload for table changes */
64
+ export interface TableChangedPayload {
65
+ table: string;
66
+ operation: 'insert' | 'update' | 'delete';
67
+ pk: { ${pkField}: ${pkType} };
68
+ data: ${pascalName} | null;
69
+ }
70
+
71
+ export const use${pascalName}Store = defineStore('${entityName}-store', () => {
72
+ const records: Ref<${pascalName}[]> = ref([]);
73
+ const loading: Ref<boolean> = ref(false);
74
+ const error: Ref<Error | null> = ref(null);
75
+
76
+ async function get(id: ${pkType}): Promise<${pascalName} | null> {
77
+ loading.value = true;
78
+ try {
79
+ const result = await ws.api.get_${entityName}({ ${pkField}: id });
80
+ return result as ${pascalName} | null;
81
+ } catch (e) {
82
+ error.value = e as Error;
83
+ throw e;
84
+ } finally {
85
+ loading.value = false;
86
+ }
87
+ }
88
+
89
+ async function save(data: Partial<${pascalName}>): Promise<${pascalName}> {
90
+ loading.value = true;
91
+ try {
92
+ const result = await ws.api.save_${entityName}(data);
93
+ return result as ${pascalName};
94
+ } catch (e) {
95
+ error.value = e as Error;
96
+ throw e;
97
+ } finally {
98
+ loading.value = false;
99
+ }
100
+ }
101
+
102
+ async function remove(id: ${pkType}): Promise<${pascalName}> {
103
+ loading.value = true;
104
+ try {
105
+ const result = await ws.api.delete_${entityName}({ ${pkField}: id });
106
+ return result as ${pascalName};
107
+ } catch (e) {
108
+ error.value = e as Error;
109
+ throw e;
110
+ } finally {
111
+ loading.value = false;
112
+ }
113
+ }
114
+
115
+ async function search(query: {
116
+ filters?: Record<string, unknown>;
117
+ sort_field?: string;
118
+ sort_order?: 'asc' | 'desc';
119
+ limit?: number;
120
+ offset?: number;
121
+ } = {}): Promise<${pascalName}[]> {
122
+ loading.value = true;
123
+ try {
124
+ const result = await ws.api.search_${entityName}(query);
125
+ records.value = result as ${pascalName}[];
126
+ return records.value;
127
+ } catch (e) {
128
+ error.value = e as Error;
129
+ throw e;
130
+ } finally {
131
+ loading.value = false;
132
+ }
133
+ }
134
+
135
+ function table_changed(payload: TableChangedPayload): void {
136
+ if (payload.table === '${entityName}') {
137
+ const pkValue = payload.pk?.${pkField};
138
+ const existingIndex = records.value.findIndex((r: ${pascalName}) => r.${pkField} === pkValue);
139
+
140
+ switch (payload.operation) {
141
+ case 'insert':
142
+ if (payload.data && existingIndex === -1) {
143
+ records.value.push(payload.data);
144
+ }
145
+ break;
146
+ case 'update':
147
+ if (payload.data && existingIndex !== -1) {
148
+ Object.assign(records.value[existingIndex], payload.data);
149
+ } else if (payload.data) {
150
+ records.value.push(payload.data);
151
+ }
152
+ break;
153
+ case 'delete':
154
+ if (existingIndex !== -1) {
155
+ records.value.splice(existingIndex, 1);
156
+ }
157
+ break;
158
+ }
159
+ }
160
+ }
161
+
162
+ return {
163
+ records,
164
+ loading,
165
+ error,
166
+ get,
167
+ save,
168
+ remove,
169
+ search,
170
+ table_changed,
171
+ };
172
+ });
173
+ `;
174
+ }
@@ -0,0 +1,58 @@
1
+ import type { SubscribableIR } from "../../shared/ir.js";
2
+
3
+ export function generateAffectedKeysFunction(name: string, subIR: SubscribableIR): string {
4
+ // Logic to determine keys based on table
5
+ // For 'post_detail' (param: post_id), if 'posts' table updates, key is 'post_detail:' + post.id
6
+
7
+ // This is a simplified generator. In reality, it needs to traverse the 'includes' graph
8
+ // to find the path from the updated table back to the root parameter.
9
+
10
+ // Example for post_detail:
11
+ // Root: posts (key: post_id)
12
+ // -> includes comments (filter: post_id=@id)
13
+
14
+ // If posts updates: key = 'post_detail:' + new.id
15
+ // If comments updates: key = 'post_detail:' + new.post_id
16
+
17
+ // We need to generate a CASE statement for p_table
18
+
19
+ const cases: string[] = [];
20
+
21
+ // 1. Root Entity Case
22
+ const rootEntity = subIR.root.entity;
23
+ // Assumption: the param key matches the root entity's PK or is mapped
24
+ // Here, param is 'post_id'. Root key is 'post_id'.
25
+ // If root key starts with @, it's a variable. If not, it's a param name.
26
+ // Wait, in blog.ts: root: { entity: 'posts', key: 'post_id' }
27
+ // This means the subscription param 'post_id' maps to the 'posts' entity.
28
+ // So if 'posts' updates, we look at its ID (assuming ID is the join key? No, 'key' is the param name).
29
+
30
+ // Simpler assumption for V2.0:
31
+ // subscription key = "sub_name:param_value"
32
+
33
+ // If root table updates, the param value is usually the PK of the root table.
34
+ cases.push(
35
+ `
36
+ WHEN '${rootEntity}' THEN
37
+ RETURN ARRAY['${name}:' || COALESCE(p_new->>'id', p_old->>'id')];
38
+ `);
39
+
40
+ // TODO: Handle related tables via traversal
41
+
42
+ return (
43
+ `
44
+ CREATE OR REPLACE FUNCTION dzql_v2.${name}_affected_keys(p_table text, p_op text, p_old jsonb, p_new jsonb)
45
+ RETURNS text[]
46
+ LANGUAGE plpgsql
47
+ IMMUTABLE
48
+ AS $$
49
+ BEGIN
50
+ CASE p_table
51
+ ${cases.join('\n')}
52
+ ELSE
53
+ RETURN ARRAY[]::text[];
54
+ END CASE;
55
+ END;
56
+ $$;
57
+ `);
58
+ }