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
@@ -1,251 +0,0 @@
1
- import { sql } from './db.js';
2
-
3
- export function metaRoute() {
4
- return async (req) => {
5
- try {
6
- // Get entity metadata from dzql.entities
7
- const entities = await sql`
8
- SELECT table_name, label_field, searchable_fields,
9
- fk_includes, notification_paths, permission_paths
10
- FROM dzql.entities
11
- ORDER BY table_name
12
- `;
13
-
14
- // Analyze foreign key relationships to determine relationship types
15
- const foreignKeys = await sql`
16
- SELECT
17
- tc.table_name,
18
- kcu.column_name,
19
- ccu.table_name AS foreign_table_name,
20
- ccu.column_name AS foreign_column_name
21
- FROM
22
- information_schema.table_constraints AS tc
23
- JOIN information_schema.key_column_usage AS kcu
24
- ON tc.constraint_name = kcu.constraint_name
25
- AND tc.table_schema = kcu.table_schema
26
- JOIN information_schema.constraint_column_usage AS ccu
27
- ON ccu.constraint_name = tc.constraint_name
28
- AND ccu.table_schema = tc.table_schema
29
- WHERE tc.constraint_type = 'FOREIGN KEY'
30
- AND tc.table_schema = 'public'
31
- ORDER BY tc.table_name, kcu.column_name
32
- `;
33
-
34
- // Get complete table schema information
35
- const schemaInfo = await sql`
36
- SELECT
37
- table_name,
38
- column_name,
39
- data_type,
40
- is_nullable,
41
- column_default,
42
- character_maximum_length,
43
- numeric_precision,
44
- numeric_scale,
45
- ordinal_position
46
- FROM information_schema.columns
47
- WHERE table_schema = 'public'
48
- AND table_name = ANY(${entities.map(e => e.table_name)})
49
- ORDER BY table_name, ordinal_position
50
- `;
51
-
52
- // Find junction tables for many-to-many relationships
53
- const junctionTables = new Set();
54
- const entityNames = new Set(entities.map(e => e.table_name));
55
-
56
- // Group foreign keys by table to identify junction tables
57
- const fksByTable = {};
58
- foreignKeys.forEach(fk => {
59
- if (!entityNames.has(fk.table_name)) return;
60
- if (!fksByTable[fk.table_name]) fksByTable[fk.table_name] = [];
61
- fksByTable[fk.table_name].push(fk);
62
- });
63
-
64
- // Identify junction tables (2+ foreign keys, minimal other fields)
65
- Object.keys(fksByTable).forEach(tableName => {
66
- const fks = fksByTable[tableName];
67
- if (fks.length >= 2) {
68
- const entity = entities.find(e => e.table_name === tableName);
69
- const searchableFields = entity?.searchable_fields || [];
70
- const nonFkFields = searchableFields.filter(field =>
71
- !fks.some(fk => fk.column_name === field)
72
- );
73
- if (nonFkFields.length <= 1) {
74
- junctionTables.add(tableName);
75
- }
76
- }
77
- });
78
-
79
- // Build relations array
80
- const relations = [];
81
-
82
- // Add foreign key relationships (both directions)
83
- foreignKeys.forEach(fk => {
84
- if (!entityNames.has(fk.table_name) || !entityNames.has(fk.foreign_table_name)) return;
85
-
86
- // Skip junction tables - they'll be handled as many-to-many
87
- if (junctionTables.has(fk.table_name)) return;
88
-
89
- // Many-to-one: child.foreign_key → parent.primary_key
90
- relations.push({
91
- type: 'many_to_one',
92
- from: `${fk.table_name}.${fk.column_name}`,
93
- to: `${fk.foreign_table_name}.${fk.foreign_column_name}`
94
- });
95
-
96
- // One-to-many: parent.primary_key ← child.foreign_key
97
- relations.push({
98
- type: 'one_to_many',
99
- from: `${fk.foreign_table_name}.${fk.foreign_column_name}`,
100
- to: `${fk.table_name}.${fk.column_name}`
101
- });
102
- });
103
-
104
- // Add many-to-many relationships through junction tables
105
- junctionTables.forEach(tableName => {
106
- const fks = fksByTable[tableName];
107
- // For each pair of foreign keys in the junction table
108
- for (let i = 0; i < fks.length; i++) {
109
- for (let j = i + 1; j < fks.length; j++) {
110
- const fk1 = fks[i];
111
- const fk2 = fks[j];
112
-
113
- // Both directions of many-to-many
114
- relations.push({
115
- type: 'many_to_many',
116
- from: `${fk1.foreign_table_name}.${fk1.foreign_column_name}`,
117
- to: `${fk2.foreign_table_name}.${fk2.foreign_column_name}`,
118
- via: `${tableName}.${fk1.column_name}.${fk2.column_name}`
119
- });
120
-
121
- relations.push({
122
- type: 'many_to_many',
123
- from: `${fk2.foreign_table_name}.${fk2.foreign_column_name}`,
124
- to: `${fk1.foreign_table_name}.${fk1.foreign_column_name}`,
125
- via: `${tableName}.${fk2.column_name}.${fk1.column_name}`
126
- });
127
- }
128
- }
129
- });
130
-
131
- // Build schema object grouped by table
132
- const schema = {};
133
- schemaInfo.forEach(col => {
134
- if (!schema[col.table_name]) {
135
- schema[col.table_name] = [];
136
- }
137
- schema[col.table_name].push({
138
- column_name: col.column_name,
139
- data_type: col.data_type,
140
- is_nullable: col.is_nullable === 'YES',
141
- column_default: col.column_default,
142
- character_maximum_length: col.character_maximum_length,
143
- numeric_precision: col.numeric_precision,
144
- numeric_scale: col.numeric_scale,
145
- ordinal_position: col.ordinal_position
146
- });
147
- });
148
-
149
- // Build navigation graph from user starting point
150
- const navigationGraph = buildNavigationGraph(entities, relations, schema);
151
-
152
- const metadata = {
153
- entities: entities,
154
- relations: relations,
155
- schema: schema,
156
- navigationGraph: navigationGraph,
157
- operations: ['get', 'save', 'delete', 'lookup', 'search'],
158
- timestamp: new Date().toISOString()
159
- };
160
-
161
- return new Response(JSON.stringify(metadata, null, 2), {
162
- headers: { 'Content-Type': 'application/json' }
163
- });
164
- } catch (error) {
165
- return new Response(JSON.stringify({ error: error.message }), {
166
- status: 500,
167
- headers: { 'Content-Type': 'application/json' }
168
- });
169
- }
170
- };
171
- }
172
-
173
- function buildNavigationGraph(entities, relations, schema) {
174
- const graph = {};
175
-
176
- // Helper to detect UI patterns from schema
177
- function getUiHints(entityName) {
178
- const entitySchema = schema[entityName] || [];
179
- const hints = { primary_view: 'table', alternate_view: 'form', temporal_fields: [], geo_fields: [] };
180
-
181
- entitySchema.forEach(col => {
182
- if (col.data_type.includes('date') || col.data_type.includes('time')) {
183
- hints.temporal_fields.push(col.column_name);
184
- }
185
- if (col.column_name.toLowerCase().includes('address') ||
186
- col.column_name.toLowerCase().includes('location')) {
187
- hints.geo_fields.push(col.column_name);
188
- hints.primary_view = 'map';
189
- }
190
- });
191
-
192
- if (hints.temporal_fields.length > 0) {
193
- hints.alternate_view = 'calendar';
194
- }
195
-
196
- return hints;
197
- }
198
-
199
- // Build navigation paths starting from user
200
- function buildPathsFrom(currentEntity, currentPath, visited, maxDepth) {
201
- if (maxDepth <= 0 || visited.has(currentEntity)) return;
202
-
203
- visited.add(currentEntity);
204
- const pathKey = currentPath.join('→');
205
-
206
- if (!graph[pathKey]) {
207
- const entity = entities.find(e => e.table_name === currentEntity);
208
- graph[pathKey] = {
209
- path: pathKey,
210
- current_entity: currentEntity,
211
- available_actions: entity ? Object.keys(entity.permission_paths) : [],
212
- navigation_options: [],
213
- ui_hints: getUiHints(currentEntity),
214
- breadcrumb: currentPath.map(p => p.split('.')[0])
215
- };
216
- }
217
-
218
- // Find all outgoing relations from current entity
219
- relations.forEach(rel => {
220
- const fromEntity = rel.from.split('.')[0];
221
- const toEntity = rel.to.split('.')[0];
222
-
223
- if (fromEntity === currentEntity && !visited.has(toEntity)) {
224
- graph[pathKey].navigation_options.push({
225
- to: toEntity,
226
- via: `${rel.from}→${rel.to}`,
227
- relationship: rel.type
228
- });
229
-
230
- // Recursively build paths
231
- const newPath = [...currentPath, rel.to];
232
- buildPathsFrom(toEntity, newPath, new Set(visited), maxDepth - 1);
233
- }
234
- });
235
-
236
- visited.delete(currentEntity);
237
- }
238
-
239
- // Start building from user
240
- const userRelations = relations.filter(rel => rel.from.startsWith('users.') || rel.to.startsWith('users.'));
241
- if (userRelations.length === 0) {
242
- // If no direct user relations, start from all entities
243
- entities.forEach(entity => {
244
- buildPathsFrom(entity.table_name, [entity.table_name + '.id'], new Set(), 3);
245
- });
246
- } else {
247
- buildPathsFrom('users', ['users.id'], new Set(), 4);
248
- }
249
-
250
- return graph;
251
- }
@@ -1,292 +0,0 @@
1
- // Suppress logger output for CLI usage - MUST be set before any imports
2
- if (!process.env.NODE_ENV) {
3
- process.env.NODE_ENV = 'production';
4
- }
5
-
6
- import { sql, db } from "./db.js";
7
-
8
- // Default user for CLI operations
9
- const DEFAULT_USER_ID = 1;
10
-
11
- /**
12
- * Discover available entities from dzql.entities table or compiled functions
13
- * @returns {Promise<Object>} Map of entity name to {label, searchable, description}
14
- */
15
- async function discoverEntities() {
16
- // First try dzql.entities table (runtime mode)
17
- const result = await sql`
18
- SELECT table_name, label_field, searchable_fields
19
- FROM dzql.entities
20
- ORDER BY table_name
21
- `;
22
-
23
- const entities = {};
24
-
25
- if (result.length > 0) {
26
- // Runtime mode - use dzql.entities table
27
- for (const row of result) {
28
- const searchFields = row.searchable_fields?.join(", ") || "none";
29
- entities[row.table_name] = {
30
- label: row.label_field,
31
- searchable: row.searchable_fields || [],
32
- description: `Entity: ${row.table_name} (label: ${row.label_field}, searchable: ${searchFields})`,
33
- };
34
- }
35
- } else {
36
- // Compiled mode - discover from function names
37
- const functions = await sql`
38
- SELECT DISTINCT substring(proname from 'search_(.+)') as entity_name
39
- FROM pg_proc
40
- WHERE pronamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')
41
- AND proname LIKE 'search_%'
42
- AND substring(proname from 'search_(.+)') IS NOT NULL
43
- ORDER BY entity_name
44
- `;
45
-
46
- for (const row of functions) {
47
- const entityName = row.entity_name;
48
- entities[entityName] = {
49
- label: 'id', // Default, since we can't know from functions alone
50
- searchable: [],
51
- description: `Entity: ${entityName} (compiled mode)`,
52
- };
53
- }
54
- }
55
-
56
- return entities;
57
- }
58
-
59
- /**
60
- * DZQL operations namespace - provides CLI-style access to DZQL operations
61
- *
62
- * Each method outputs JSON to console and calls sql.end() before returning,
63
- * making instances single-use. On error, methods call process.exit(1).
64
- *
65
- * Usage in tasks.js:
66
- * ```js
67
- * import { DzqlNamespace } from 'dzql/namespace';
68
- *
69
- * export class Tasks {
70
- * constructor() {
71
- * this.dzql = new DzqlNamespace();
72
- * }
73
- * }
74
- * ```
75
- */
76
- export class DzqlNamespace {
77
- /**
78
- * @param {number} [userId=1] - User ID for permission checks
79
- */
80
- constructor(userId = DEFAULT_USER_ID) {
81
- this.userId = userId;
82
- }
83
-
84
- /**
85
- * List all available entities
86
- * @returns {Promise<void>} Outputs JSON to console
87
- */
88
- async entities(c) {
89
- try {
90
- const entities = await discoverEntities();
91
- console.log(JSON.stringify({ success: true, entities }, null, 2));
92
- await sql.end();
93
- } catch (error) {
94
- console.error(
95
- JSON.stringify({ success: false, error: error.message }, null, 2),
96
- );
97
- await sql.end();
98
- process.exit(1);
99
- }
100
- }
101
-
102
- /**
103
- * Search an entity
104
- * @example invj dzql:search venues '{"query": "test"}'
105
- * @param {string} entity - Entity/table name to search
106
- * @param {string} [argsJson] - JSON string with search args (query, limit, offset, filters)
107
- */
108
- async search(c, entity, argsJson = "{}") {
109
- if (!entity) {
110
- console.error("Error: entity name required");
111
- console.error("Usage: invokej dzql.search <entity> '<json_args>'");
112
- console.error(
113
- 'Example: invokej dzql.search organisations \'{"query": "test"}\'',
114
- );
115
- await sql.end();
116
- process.exit(1);
117
- }
118
-
119
- let args;
120
- try {
121
- args = JSON.parse(argsJson);
122
- } catch (e) {
123
- console.error("Error: arguments must be valid JSON");
124
- await sql.end();
125
- process.exit(1);
126
- }
127
-
128
- try {
129
- const result = await db.api.search[entity](args, this.userId);
130
- console.log(JSON.stringify({ success: true, result }, null, 2));
131
- await sql.end();
132
- } catch (error) {
133
- console.error(
134
- JSON.stringify({ success: false, error: error.message }, null, 2),
135
- );
136
- await sql.end();
137
- process.exit(1);
138
- }
139
- }
140
-
141
- /**
142
- * Get entity by ID
143
- * @example invj dzql:get venues '{"id": 1}'
144
- * @param {string} entity - Entity/table name
145
- * @param {string} [argsJson] - JSON string with {id: number}
146
- */
147
- async get(c, entity, argsJson = "{}") {
148
- if (!entity) {
149
- console.error("Error: entity name required");
150
- console.error("Usage: invokej dzql.get <entity> '<json_args>'");
151
- console.error("Example: invokej dzql.get venues '{\"id\": 1}'");
152
- await sql.end();
153
- process.exit(1);
154
- }
155
-
156
- let args;
157
- try {
158
- args = JSON.parse(argsJson);
159
- } catch (e) {
160
- console.error("Error: arguments must be valid JSON");
161
- await sql.end();
162
- process.exit(1);
163
- }
164
-
165
- try {
166
- const result = await db.api.get[entity](args, this.userId);
167
- console.log(JSON.stringify({ success: true, result }, null, 2));
168
- await sql.end();
169
- } catch (error) {
170
- console.error(
171
- JSON.stringify({ success: false, error: error.message }, null, 2),
172
- );
173
- await sql.end();
174
- process.exit(1);
175
- }
176
- }
177
-
178
- /**
179
- * Save (create or update) entity
180
- * @example invj dzql:save venues '{"name": "New Venue", "org_id": 1}'
181
- * @param {string} entity - Entity/table name
182
- * @param {string} [argsJson] - JSON string with entity data (include id to update, omit to create)
183
- */
184
- async save(c, entity, argsJson = "{}") {
185
- if (!entity) {
186
- console.error("Error: entity name required");
187
- console.error("Usage: invokej dzql.save <entity> '<json_args>'");
188
- console.error(
189
- 'Example: invokej dzql.save venues \'{"name": "Test Venue", "org_id": 1}\'',
190
- );
191
- await sql.end();
192
- process.exit(1);
193
- }
194
-
195
- let args;
196
- try {
197
- args = JSON.parse(argsJson);
198
- } catch (e) {
199
- console.error("Error: arguments must be valid JSON");
200
- await sql.end();
201
- process.exit(1);
202
- }
203
-
204
- try {
205
- const result = await db.api.save[entity](args, this.userId);
206
- console.log(JSON.stringify({ success: true, result }, null, 2));
207
- await sql.end();
208
- } catch (error) {
209
- console.error(
210
- JSON.stringify({ success: false, error: error.message }, null, 2),
211
- );
212
- await sql.end();
213
- process.exit(1);
214
- }
215
- }
216
-
217
- /**
218
- * Delete entity by ID
219
- * @example invj dzql:delete venues '{"id": 1}'
220
- * @param {string} entity - Entity/table name
221
- * @param {string} [argsJson] - JSON string with {id: number}
222
- */
223
- async delete(c, entity, argsJson = "{}") {
224
- if (!entity) {
225
- console.error("Error: entity name required");
226
- console.error("Usage: invokej dzql.delete <entity> '<json_args>'");
227
- console.error("Example: invokej dzql.delete venues '{\"id\": 1}'");
228
- await sql.end();
229
- process.exit(1);
230
- }
231
-
232
- let args;
233
- try {
234
- args = JSON.parse(argsJson);
235
- } catch (e) {
236
- console.error("Error: arguments must be valid JSON");
237
- await sql.end();
238
- process.exit(1);
239
- }
240
-
241
- try {
242
- const result = await db.api.delete[entity](args, this.userId);
243
- console.log(JSON.stringify({ success: true, result }, null, 2));
244
- await sql.end();
245
- } catch (error) {
246
- console.error(
247
- JSON.stringify({ success: false, error: error.message }, null, 2),
248
- );
249
- await sql.end();
250
- process.exit(1);
251
- }
252
- }
253
-
254
- /**
255
- * Lookup entity (for dropdowns/autocomplete)
256
- * @example invj dzql:lookup organisations '{"query": "acme"}'
257
- * @param {string} entity - Entity/table name
258
- * @param {string} [argsJson] - JSON string with {query: string, limit?: number}
259
- */
260
- async lookup(c, entity, argsJson = "{}") {
261
- if (!entity) {
262
- console.error("Error: entity name required");
263
- console.error("Usage: invokej dzql.lookup <entity> '<json_args>'");
264
- console.error(
265
- 'Example: invokej dzql.lookup organisations \'{"query": "acme"}\'',
266
- );
267
- await sql.end();
268
- process.exit(1);
269
- }
270
-
271
- let args;
272
- try {
273
- args = JSON.parse(argsJson);
274
- } catch (e) {
275
- console.error("Error: arguments must be valid JSON");
276
- await sql.end();
277
- process.exit(1);
278
- }
279
-
280
- try {
281
- const result = await db.api.lookup[entity](args, this.userId);
282
- console.log(JSON.stringify({ success: true, result }, null, 2));
283
- await sql.end();
284
- } catch (error) {
285
- console.error(
286
- JSON.stringify({ success: false, error: error.message }, null, 2),
287
- );
288
- await sql.end();
289
- process.exit(1);
290
- }
291
- }
292
- }