dzql 0.5.31 → 0.5.33

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.5.31",
3
+ "version": "0.5.33",
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",
@@ -52,7 +52,7 @@ export class GraphRulesCodegen {
52
52
 
53
53
  const actionBlocks = [];
54
54
  for (const action of actions) {
55
- const actionSQL = this._generateAction(action, ruleName, description);
55
+ const actionSQL = this._generateAction(action, ruleName, description, operation);
56
56
  if (actionSQL) {
57
57
  actionBlocks.push(actionSQL);
58
58
  }
@@ -99,29 +99,33 @@ $$ LANGUAGE plpgsql SECURITY DEFINER;`;
99
99
 
100
100
  /**
101
101
  * Generate SQL for a single action
102
+ * @param {Object} action - The action configuration
103
+ * @param {string} ruleName - The rule name
104
+ * @param {string} description - The rule description
105
+ * @param {string} operation - The operation context ('create', 'update', 'delete')
102
106
  * @private
103
107
  */
104
- _generateAction(action, ruleName, description) {
108
+ _generateAction(action, ruleName, description, operation) {
105
109
  const comment = ` -- ${description}`;
106
110
 
107
111
  switch (action.type) {
108
112
  case 'create':
109
- return this._generateCreateAction(action, comment);
113
+ return this._generateCreateAction(action, comment, operation);
110
114
 
111
115
  case 'update':
112
- return this._generateUpdateAction(action, comment);
116
+ return this._generateUpdateAction(action, comment, operation);
113
117
 
114
118
  case 'delete':
115
- return this._generateDeleteAction(action, comment);
119
+ return this._generateDeleteAction(action, comment, operation);
116
120
 
117
121
  case 'validate':
118
- return this._generateValidateAction(action, comment);
122
+ return this._generateValidateAction(action, comment, operation);
119
123
 
120
124
  case 'execute':
121
- return this._generateExecuteAction(action, comment);
125
+ return this._generateExecuteAction(action, comment, operation);
122
126
 
123
127
  case 'notify':
124
- return this._generateNotifyAction(action, comment);
128
+ return this._generateNotifyAction(action, comment, operation);
125
129
 
126
130
  default:
127
131
  console.warn('Unknown action type:', action.type);
@@ -133,7 +137,7 @@ $$ LANGUAGE plpgsql SECURITY DEFINER;`;
133
137
  * Generate CREATE action
134
138
  * @private
135
139
  */
136
- _generateCreateAction(action, comment) {
140
+ _generateCreateAction(action, comment, operation) {
137
141
  const entity = action.entity;
138
142
  const data = action.data;
139
143
 
@@ -142,7 +146,7 @@ $$ LANGUAGE plpgsql SECURITY DEFINER;`;
142
146
 
143
147
  for (const [field, value] of Object.entries(data)) {
144
148
  fields.push(field);
145
- values.push(this._resolveValue(value));
149
+ values.push(this._resolveValue(value, operation));
146
150
  }
147
151
 
148
152
  return `${comment}
@@ -154,19 +158,19 @@ $$ LANGUAGE plpgsql SECURITY DEFINER;`;
154
158
  * Generate UPDATE action
155
159
  * @private
156
160
  */
157
- _generateUpdateAction(action, comment) {
161
+ _generateUpdateAction(action, comment, operation) {
158
162
  const entity = action.entity;
159
163
  const data = action.data;
160
164
  const match = action.match;
161
165
 
162
166
  const setClauses = [];
163
167
  for (const [field, value] of Object.entries(data)) {
164
- setClauses.push(`${field} = ${this._resolveValue(value)}`);
168
+ setClauses.push(`${field} = ${this._resolveValue(value, operation)}`);
165
169
  }
166
170
 
167
171
  const whereClauses = [];
168
172
  for (const [field, value] of Object.entries(match)) {
169
- whereClauses.push(`${field} = ${this._resolveValue(value)}`);
173
+ whereClauses.push(`${field} = ${this._resolveValue(value, operation)}`);
170
174
  }
171
175
 
172
176
  return `${comment}
@@ -179,13 +183,13 @@ $$ LANGUAGE plpgsql SECURITY DEFINER;`;
179
183
  * Generate DELETE action
180
184
  * @private
181
185
  */
182
- _generateDeleteAction(action, comment) {
186
+ _generateDeleteAction(action, comment, operation) {
183
187
  const entity = action.entity;
184
188
  const match = action.match;
185
189
 
186
190
  const whereClauses = [];
187
191
  for (const [field, value] of Object.entries(match)) {
188
- whereClauses.push(`${field} = ${this._resolveValue(value)}`);
192
+ whereClauses.push(`${field} = ${this._resolveValue(value, operation)}`);
189
193
  }
190
194
 
191
195
  return `${comment}
@@ -197,14 +201,14 @@ $$ LANGUAGE plpgsql SECURITY DEFINER;`;
197
201
  * Generate VALIDATE action
198
202
  * @private
199
203
  */
200
- _generateValidateAction(action, comment) {
204
+ _generateValidateAction(action, comment, operation) {
201
205
  const functionName = action.function;
202
206
  const params = action.params || {};
203
207
  const errorMessage = action.error_message || 'Validation failed';
204
208
 
205
209
  const paramList = [];
206
210
  for (const [key, value] of Object.entries(params)) {
207
- paramList.push(`${key} => ${this._resolveValue(value)}`);
211
+ paramList.push(`${key} => ${this._resolveValue(value, operation)}`);
208
212
  }
209
213
 
210
214
  const paramSQL = paramList.length > 0 ? paramList.join(', ') : '';
@@ -219,13 +223,13 @@ $$ LANGUAGE plpgsql SECURITY DEFINER;`;
219
223
  * Generate EXECUTE action
220
224
  * @private
221
225
  */
222
- _generateExecuteAction(action, comment) {
226
+ _generateExecuteAction(action, comment, operation) {
223
227
  const functionName = action.function;
224
228
  const params = action.params || {};
225
229
 
226
230
  const paramList = [];
227
231
  for (const [key, value] of Object.entries(params)) {
228
- paramList.push(`${key} => ${this._resolveValue(value)}`);
232
+ paramList.push(`${key} => ${this._resolveValue(value, operation)}`);
229
233
  }
230
234
 
231
235
  const paramSQL = paramList.length > 0 ? paramList.join(', ') : '';
@@ -253,11 +257,16 @@ $$ LANGUAGE plpgsql SECURITY DEFINER;`;
253
257
  * Creates an event that will be broadcast to specified users
254
258
  * @private
255
259
  */
256
- _generateNotifyAction(action, comment) {
260
+ _generateNotifyAction(action, comment, operation) {
257
261
  const users = action.users || [];
258
262
  const message = action.message || '';
259
263
  const data = action.data || {};
260
264
 
265
+ // Determine the correct record variable based on operation
266
+ const recordVar = operation === 'delete' ? 'p_old_record'
267
+ : operation === 'update' ? 'p_new_record'
268
+ : 'p_record';
269
+
261
270
  // Build user ID array resolution
262
271
  let userIdSQL = 'ARRAY[]::INT[]';
263
272
 
@@ -269,10 +278,10 @@ $$ LANGUAGE plpgsql SECURITY DEFINER;`;
269
278
  if (userPath.startsWith('@') && !userPath.includes('->')) {
270
279
  // Simple field reference: @author_id
271
280
  const fieldName = userPath.substring(1);
272
- userPaths.push(`(p_record->>'${fieldName}')::int`);
281
+ userPaths.push(`(${recordVar}->>'${fieldName}')::int`);
273
282
  } else if (userPath.startsWith('@') && userPath.includes('->')) {
274
283
  // Complex path: @post_id->posts.author_id - use runtime resolver
275
- userPaths.push(`dzql.resolve_notification_path('${this.tableName}', p_record, '${userPath}')`);
284
+ userPaths.push(`dzql.resolve_notification_path('${this.tableName}', ${recordVar}, '${userPath}')`);
276
285
  } else {
277
286
  // Literal user ID
278
287
  userPaths.push(`${userPath}`);
@@ -301,19 +310,19 @@ $$ LANGUAGE plpgsql SECURITY DEFINER;`;
301
310
  dataFields.push(`'table', '${this.tableName}'`);
302
311
 
303
312
  if (message) {
304
- dataFields.push(`'message', ${this._resolveValue(message)}`);
313
+ dataFields.push(`'message', ${this._resolveValue(message, operation)}`);
305
314
  }
306
315
 
307
316
  // Add custom data fields
308
317
  for (const [key, value] of Object.entries(data)) {
309
- dataFields.push(`'${key}', ${this._resolveValue(value)}`);
318
+ dataFields.push(`'${key}', ${this._resolveValue(value, operation)}`);
310
319
  }
311
320
 
312
321
  const dataSQL = dataFields.length > 0
313
322
  ? `jsonb_build_object(${dataFields.join(', ')})`
314
323
  : "'{}'::jsonb";
315
324
 
316
- const pkBuildObject = this._generatePKBuildObject('p_record');
325
+ const pkBuildObject = this._generatePKBuildObject(recordVar);
317
326
 
318
327
  return `${comment}
319
328
  -- Create notification event
@@ -386,9 +395,11 @@ $$ LANGUAGE plpgsql SECURITY DEFINER;`;
386
395
 
387
396
  /**
388
397
  * Resolve a value (variable reference or literal)
398
+ * @param {string|number} value - The value to resolve
399
+ * @param {string} operation - The operation context ('create', 'update', 'delete')
389
400
  * @private
390
401
  */
391
- _resolveValue(value) {
402
+ _resolveValue(value, operation = 'create') {
392
403
  if (typeof value !== 'string') {
393
404
  // Number or other type
394
405
  return value;
@@ -410,8 +421,14 @@ $$ LANGUAGE plpgsql SECURITY DEFINER;`;
410
421
  return 'NOW()';
411
422
 
412
423
  default:
413
- // Field reference from record
414
- return `(p_record->>'${varName}')`;
424
+ // Field reference from record - use correct variable based on operation
425
+ if (operation === 'delete') {
426
+ return `(p_old_record->>'${varName}')`;
427
+ } else if (operation === 'update') {
428
+ return `(p_new_record->>'${varName}')`;
429
+ } else {
430
+ return `(p_record->>'${varName}')`;
431
+ }
415
432
  }
416
433
  }
417
434
 
@@ -575,7 +575,7 @@ BEGIN
575
575
  WHEN 'lte' THEN
576
576
  v_where_clause := v_where_clause || format(' AND %I <= %L', v_field, v_value #>> '{}');
577
577
  WHEN 'in' THEN
578
- v_where_clause := v_where_clause || format(' AND %I = ANY(%L::TEXT[])', v_field,
578
+ v_where_clause := v_where_clause || format(' AND %I::TEXT = ANY(%L)', v_field,
579
579
  (SELECT array_agg(value #>> '{}') FROM jsonb_array_elements(v_value) AS value));
580
580
  WHEN 'ilike' THEN
581
581
  v_where_clause := v_where_clause || format(' AND %I ILIKE %L', v_field, v_value #>> '{}');
@@ -61,7 +61,7 @@ BEGIN
61
61
 
62
62
  WHEN 'in' THEN
63
63
  IF jsonb_typeof(l_op_value) = 'array' THEN
64
- l_clauses := l_clauses || format('%I = ANY(%L)',
64
+ l_clauses := l_clauses || format('%I::TEXT = ANY(%L)',
65
65
  p_column_name,
66
66
  ARRAY(SELECT jsonb_array_elements_text(l_op_value))
67
67
  );
@@ -69,7 +69,7 @@ BEGIN
69
69
 
70
70
  WHEN 'not_in' THEN
71
71
  IF jsonb_typeof(l_op_value) = 'array' THEN
72
- l_clauses := l_clauses || format('%I != ALL(%L)',
72
+ l_clauses := l_clauses || format('%I::TEXT != ALL(%L)',
73
73
  p_column_name,
74
74
  ARRAY(SELECT jsonb_array_elements_text(l_op_value))
75
75
  );