dzql 0.4.0 → 0.4.2
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.
- package/package.json +1 -1
- package/src/server/db.js +64 -6
- package/src/server/logger.js +17 -4
- package/src/server/namespace.js +37 -8
package/package.json
CHANGED
package/src/server/db.js
CHANGED
|
@@ -35,7 +35,10 @@ export const listen_sql = postgres(DATABASE_URL, {
|
|
|
35
35
|
onnotice: process.env.NODE_ENV === 'test' ? () => {} : undefined,
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
// Only log connection info in development
|
|
39
|
+
if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
|
|
40
|
+
dbLogger.info(`Database connected: ${DATABASE_URL.replace(/\/\/.*@/, '//***@')}`);
|
|
41
|
+
}
|
|
39
42
|
|
|
40
43
|
// Cache for function parameter metadata
|
|
41
44
|
const functionParamCache = new Map();
|
|
@@ -170,13 +173,68 @@ export async function setupListeners(callback) {
|
|
|
170
173
|
}
|
|
171
174
|
}
|
|
172
175
|
|
|
173
|
-
// DZQL Generic Operations
|
|
176
|
+
// DZQL Generic Operations - Try compiled functions first, fall back to generic_exec
|
|
174
177
|
export async function callDZQLOperation(operation, entity, args, userId) {
|
|
175
178
|
dbLogger.trace(`DZQL ${operation}.${entity} for user ${userId}`);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
179
|
+
|
|
180
|
+
const compiledFunctionName = `${operation}_${entity}`;
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
// Try compiled function first
|
|
184
|
+
// Different operations have different signatures:
|
|
185
|
+
// - search: search_entity(p_user_id, p_filters, p_search, p_sort, p_page, p_limit)
|
|
186
|
+
// - get: get_entity(p_user_id, p_id, p_on_date)
|
|
187
|
+
// - save: save_entity(p_user_id, p_data, p_on_date)
|
|
188
|
+
// - delete: delete_entity(p_user_id, p_id)
|
|
189
|
+
// - lookup: lookup_entity(p_user_id, p_term, p_limit)
|
|
190
|
+
|
|
191
|
+
if (operation === 'search') {
|
|
192
|
+
// Extract search parameters from args
|
|
193
|
+
const filters = args.filters || args.p_filters || {};
|
|
194
|
+
const search = args.search || null;
|
|
195
|
+
const sort = args.sort || null;
|
|
196
|
+
const page = args.page || 1;
|
|
197
|
+
const limit = args.limit || 25;
|
|
198
|
+
|
|
199
|
+
const result = await sql.unsafe(`
|
|
200
|
+
SELECT ${compiledFunctionName}($1::int, $2::jsonb, $3::text, $4::jsonb, $5::int, $6::int) as result
|
|
201
|
+
`, [userId, filters, search, sort, page, limit]);
|
|
202
|
+
return result[0].result;
|
|
203
|
+
} else if (operation === 'get') {
|
|
204
|
+
const result = await sql.unsafe(`
|
|
205
|
+
SELECT ${compiledFunctionName}($1::int, $2::int, NULL) as result
|
|
206
|
+
`, [userId, args.id]);
|
|
207
|
+
return result[0].result;
|
|
208
|
+
} else if (operation === 'save') {
|
|
209
|
+
const result = await sql.unsafe(`
|
|
210
|
+
SELECT ${compiledFunctionName}($1::int, $2::jsonb, NULL) as result
|
|
211
|
+
`, [userId, args]);
|
|
212
|
+
return result[0].result;
|
|
213
|
+
} else if (operation === 'delete') {
|
|
214
|
+
const result = await sql.unsafe(`
|
|
215
|
+
SELECT ${compiledFunctionName}($1::int, $2::int) as result
|
|
216
|
+
`, [userId, args.id]);
|
|
217
|
+
return result[0].result;
|
|
218
|
+
} else if (operation === 'lookup') {
|
|
219
|
+
const result = await sql.unsafe(`
|
|
220
|
+
SELECT ${compiledFunctionName}($1::int, $2::text, $3::int) as result
|
|
221
|
+
`, [userId, args.term || '', args.limit || 10]);
|
|
222
|
+
return result[0].result;
|
|
223
|
+
} else {
|
|
224
|
+
throw new Error(`Unknown operation: ${operation}`);
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
// If compiled function doesn't exist, fall back to generic_exec
|
|
228
|
+
if (error.message?.includes('does not exist') || error.code === '42883') {
|
|
229
|
+
dbLogger.trace(`Compiled function ${compiledFunctionName} not found, trying generic_exec`);
|
|
230
|
+
const result = await sql`
|
|
231
|
+
SELECT dzql.generic_exec(${operation}, ${entity}, ${args}, ${userId}) as result
|
|
232
|
+
`;
|
|
233
|
+
return result[0].result;
|
|
234
|
+
}
|
|
235
|
+
// Re-throw other errors
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
180
238
|
}
|
|
181
239
|
|
|
182
240
|
// DZQL nested proxy factory
|
package/src/server/logger.js
CHANGED
|
@@ -12,6 +12,15 @@ const LOG_LEVELS = {
|
|
|
12
12
|
// Default log level from environment or INFO
|
|
13
13
|
const DEFAULT_LEVEL = process.env.LOG_LEVEL?.toUpperCase() || "INFO";
|
|
14
14
|
|
|
15
|
+
// Detect if running in CLI context (invokej/tasks.js)
|
|
16
|
+
const isCliContext = () => {
|
|
17
|
+
// Check if main module contains 'tasks.js' or 'invokej'
|
|
18
|
+
const mainModule = process.argv[1] || '';
|
|
19
|
+
return mainModule.includes('tasks.js') ||
|
|
20
|
+
mainModule.includes('invokej') ||
|
|
21
|
+
mainModule.includes('invj');
|
|
22
|
+
};
|
|
23
|
+
|
|
15
24
|
// Parse LOG_CATEGORIES from environment
|
|
16
25
|
// Format: "ws:debug,db:trace,auth:info" or "*:debug" for all
|
|
17
26
|
const parseCategories = () => {
|
|
@@ -20,8 +29,9 @@ const parseCategories = () => {
|
|
|
20
29
|
|
|
21
30
|
if (!envCategories) {
|
|
22
31
|
// Default settings for development vs production
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
// CLI context defaults to ERROR level unless explicitly configured
|
|
33
|
+
if (process.env.NODE_ENV === "production" || isCliContext()) {
|
|
34
|
+
categories["*"] = LOG_LEVELS.ERROR; // Only errors in production/CLI
|
|
25
35
|
} else if (process.env.NODE_ENV === "test") {
|
|
26
36
|
categories["*"] = LOG_LEVELS.ERROR;
|
|
27
37
|
} else {
|
|
@@ -220,8 +230,11 @@ export const timed = async (category, operation, fn) => {
|
|
|
220
230
|
}
|
|
221
231
|
};
|
|
222
232
|
|
|
223
|
-
// Log configuration on startup (only in development)
|
|
224
|
-
if (
|
|
233
|
+
// Log configuration on startup (only in development and when explicitly debugging)
|
|
234
|
+
// Suppress banner if LOG_CATEGORIES is not set (user doesn't care about logging config)
|
|
235
|
+
if (process.env.NODE_ENV !== "production" &&
|
|
236
|
+
process.env.NODE_ENV !== "test" &&
|
|
237
|
+
process.env.LOG_CATEGORIES) {
|
|
225
238
|
const config = getConfig();
|
|
226
239
|
console.log(colors.bright + "=== Logger Configuration ===" + colors.reset);
|
|
227
240
|
console.log("Categories:", config.categories);
|
package/src/server/namespace.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
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
|
+
|
|
1
6
|
import { sql, db } from "./db.js";
|
|
2
7
|
|
|
3
8
|
// Default user for CLI operations
|
|
4
9
|
const DEFAULT_USER_ID = 1;
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
|
-
* Discover available entities from dzql.entities table
|
|
12
|
+
* Discover available entities from dzql.entities table or compiled functions
|
|
8
13
|
*/
|
|
9
14
|
async function discoverEntities() {
|
|
15
|
+
// First try dzql.entities table (runtime mode)
|
|
10
16
|
const result = await sql`
|
|
11
17
|
SELECT table_name, label_field, searchable_fields
|
|
12
18
|
FROM dzql.entities
|
|
@@ -14,13 +20,36 @@ async function discoverEntities() {
|
|
|
14
20
|
`;
|
|
15
21
|
|
|
16
22
|
const entities = {};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
entities
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
if (result.length > 0) {
|
|
25
|
+
// Runtime mode - use dzql.entities table
|
|
26
|
+
for (const row of result) {
|
|
27
|
+
const searchFields = row.searchable_fields?.join(", ") || "none";
|
|
28
|
+
entities[row.table_name] = {
|
|
29
|
+
label: row.label_field,
|
|
30
|
+
searchable: row.searchable_fields || [],
|
|
31
|
+
description: `Entity: ${row.table_name} (label: ${row.label_field}, searchable: ${searchFields})`,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
// Compiled mode - discover from function names
|
|
36
|
+
const functions = await sql`
|
|
37
|
+
SELECT DISTINCT substring(proname from 'search_(.+)') as entity_name
|
|
38
|
+
FROM pg_proc
|
|
39
|
+
WHERE pronamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')
|
|
40
|
+
AND proname LIKE 'search_%'
|
|
41
|
+
AND substring(proname from 'search_(.+)') IS NOT NULL
|
|
42
|
+
ORDER BY entity_name
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
for (const row of functions) {
|
|
46
|
+
const entityName = row.entity_name;
|
|
47
|
+
entities[entityName] = {
|
|
48
|
+
label: 'id', // Default, since we can't know from functions alone
|
|
49
|
+
searchable: [],
|
|
50
|
+
description: `Entity: ${entityName} (compiled mode)`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
24
53
|
}
|
|
25
54
|
|
|
26
55
|
return entities;
|