dzql 0.6.21 → 0.6.23

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.6.21",
3
+ "version": "0.6.23",
4
4
  "description": "Database-first real-time framework with TypeScript support",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,5 +1,36 @@
1
1
  import type { GraphRuleIR } from "../../shared/ir.js";
2
2
 
3
+ /**
4
+ * Parses params that may be a string or object into a normalized object.
5
+ * String format: "field1=source1,field2,field3=source3"
6
+ * - "field=source" maps field to @source
7
+ * - "field" alone maps field to @field (or @id for the new record's id)
8
+ */
9
+ function parseParams(params: Record<string, string> | string | undefined): Record<string, string> {
10
+ if (!params) return {};
11
+
12
+ if (typeof params === 'object') {
13
+ return params;
14
+ }
15
+
16
+ // Parse string format: "org_id=@id,user_id=@user_id" or "organisation_id=sponsor_id,campaign_id"
17
+ const result: Record<string, string> = {};
18
+ for (const part of params.split(',')) {
19
+ const trimmed = part.trim();
20
+ if (!trimmed) continue;
21
+
22
+ if (trimmed.includes('=')) {
23
+ const [field, source] = trimmed.split('=').map(s => s.trim());
24
+ // Ensure source starts with @ for variable references
25
+ result[field] = source.startsWith('@') ? source : `@${source}`;
26
+ } else {
27
+ // Field alone means use @field as source (or @id for 'id')
28
+ result[trimmed] = `@${trimmed}`;
29
+ }
30
+ }
31
+ return result;
32
+ }
33
+
3
34
  /**
4
35
  * Resolves a value reference (like @id, @user_id, @before.field, @after.field)
5
36
  * into the appropriate SQL expression based on the trigger context.
@@ -110,7 +141,7 @@ export function compileGraphRules(entity: string, trigger: string, rules: GraphR
110
141
  // === REACTOR ===
111
142
  if (rule.action === 'reactor') {
112
143
  const name = rule.target;
113
- const params = rule.params || {};
144
+ const params = parseParams(rule.params);
114
145
 
115
146
  // Build JSON object using jsonb_build_object
116
147
  const jsonArgs: string[] = [];
@@ -139,7 +170,7 @@ export function compileGraphRules(entity: string, trigger: string, rules: GraphR
139
170
  // === CREATE SIDE EFFECT ===
140
171
  if (rule.action === 'create') {
141
172
  const target = rule.target;
142
- const data = rule.params || {};
173
+ const data = parseParams(rule.params);
143
174
  const cols: string[] = [];
144
175
  const vals: string[] = [];
145
176
 
@@ -159,8 +190,8 @@ export function compileGraphRules(entity: string, trigger: string, rules: GraphR
159
190
  // === UPDATE SIDE EFFECT ===
160
191
  if (rule.action === 'update') {
161
192
  const target = rule.target;
162
- const data = rule.params || {};
163
- const match = rule.match || {};
193
+ const data = parseParams(rule.params);
194
+ const match = parseParams(rule.match);
164
195
 
165
196
  const setClauses: string[] = [];
166
197
  for (const [key, val] of Object.entries(data)) {
@@ -186,7 +217,11 @@ export function compileGraphRules(entity: string, trigger: string, rules: GraphR
186
217
  // === DELETE CASCADE ===
187
218
  if (rule.action === 'delete') {
188
219
  const target = rule.target;
189
- const match = rule.match || rule.params || {};
220
+ // Try match first, fall back to params
221
+ let match = parseParams(rule.match);
222
+ if (Object.keys(match).length === 0) {
223
+ match = parseParams(rule.params);
224
+ }
190
225
  const whereClauses: string[] = [];
191
226
 
192
227
  for (const [key, val] of Object.entries(match)) {
@@ -204,7 +239,7 @@ export function compileGraphRules(entity: string, trigger: string, rules: GraphR
204
239
  // === VALIDATE ===
205
240
  if (rule.action === 'validate') {
206
241
  const functionName = rule.target;
207
- const params = rule.params || {};
242
+ const params = parseParams(rule.params);
208
243
  const errorMessage = rule.error_message || 'Validation failed';
209
244
 
210
245
  const paramList: string[] = [];
@@ -224,7 +259,7 @@ export function compileGraphRules(entity: string, trigger: string, rules: GraphR
224
259
  // === EXECUTE ===
225
260
  if (rule.action === 'execute') {
226
261
  const functionName = rule.target;
227
- const params = rule.params || {};
262
+ const params = parseParams(rule.params);
228
263
 
229
264
  const paramList: string[] = [];
230
265
  for (const [key, val] of Object.entries(params)) {
@@ -203,14 +203,32 @@ function buildFinalExistsCheck(finalHop: string, valueExpr: string): string {
203
203
  conditions.push(`${targetTable}.id = ${valueExpr}`);
204
204
  }
205
205
 
206
- // Temporal/active condition
206
+ // Temporal/active condition - may have multiple comma-separated parts
207
+ // e.g., {active} or {role='admin'} or {active,role=admin}
207
208
  if (condition) {
208
- // Handle simple boolean condition like {active}
209
- if (condition.match(/^[a-zA-Z0-9_]+$/)) {
210
- conditions.push(`${targetTable}.${condition} = true`);
211
- } else {
212
- // Handle complex condition like {role='admin'}
213
- conditions.push(`${targetTable}.${condition}`);
209
+ // Split by comma for multiple conditions
210
+ const conditionParts = condition.split(',').map(c => c.trim());
211
+ for (const part of conditionParts) {
212
+ // Handle simple boolean condition like {active}
213
+ if (part.match(/^[a-zA-Z0-9_]+$/)) {
214
+ conditions.push(`${targetTable}.${part} = true`);
215
+ } else {
216
+ // Handle complex condition like role='admin' or role=admin
217
+ // Convert role=admin to role = 'admin' for proper SQL
218
+ const eqMatch = part.match(/^([a-zA-Z0-9_]+)=(.+)$/);
219
+ if (eqMatch) {
220
+ const field = eqMatch[1];
221
+ let value = eqMatch[2];
222
+ // If value is already quoted, use as-is; otherwise quote it
223
+ if (!value.startsWith("'") && !value.match(/^\d+$/)) {
224
+ value = `'${value}'`;
225
+ }
226
+ conditions.push(`${targetTable}.${field} = ${value}`);
227
+ } else {
228
+ // Fallback: use as-is (e.g., complex expression)
229
+ conditions.push(`${targetTable}.${part}`);
230
+ }
231
+ }
214
232
  }
215
233
  }
216
234
 
@@ -224,4 +242,4 @@ function buildFinalExistsCheck(finalHop: string, valueExpr: string): string {
224
242
  const whereClause = conditions.join(' AND ');
225
243
 
226
244
  return `EXISTS (SELECT 1 FROM ${targetTable} WHERE ${whereClause})`;
227
- }
245
+ }