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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dzql",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "PostgreSQL-powered framework with zero boilerplate CRUD operations and real-time WebSocket synchronization",
5
5
  "type": "module",
6
6
  "main": "src/server/index.js",
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
- dbLogger.info(`Database connected: ${DATABASE_URL.replace(/\/\/.*@/, '//***@')}`);
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
- const result = await sql`
177
- SELECT dzql.generic_exec(${operation}, ${entity}, ${args}, ${userId}) as result
178
- `;
179
- return result[0].result;
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
@@ -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
- if (process.env.NODE_ENV === "production") {
24
- categories["*"] = LOG_LEVELS.WARN;
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 (process.env.NODE_ENV !== "production" && process.env.NODE_ENV !== "test") {
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);
@@ -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
- for (const row of result) {
18
- const searchFields = row.searchable_fields?.join(", ") || "none";
19
- entities[row.table_name] = {
20
- label: row.label_field,
21
- searchable: row.searchable_fields || [],
22
- description: `Entity: ${row.table_name} (label: ${row.label_field}, searchable: ${searchFields})`,
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;