dzql 0.6.29 → 0.6.30

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/docs/for_ai.md CHANGED
@@ -44,11 +44,16 @@ SUBSCRIBABLE PATTERN
44
44
  ====================
45
45
  sub_name: {
46
46
  params: { param: 'type' },
47
- root: { entity: 'table', key: 'param' },
47
+ root: { entity: 'table', key: 'param' }, // key must be in params, or '@user_id', or empty for lists
48
48
  includes: { rel: { entity: 'table', includes: {...} } },
49
49
  scopeTables: ['all', 'affected', 'tables'],
50
50
  canSubscribe: ['permission_path']
51
51
  }
52
+
53
+ ROOT KEY RULES:
54
+ - key: 'venue_id' -> must have params: { venue_id: 'int' }
55
+ - key: '@user_id' -> uses current user, no param needed
56
+ - key: '' or omit -> list subscribable (returns filtered array)
52
57
  ```
53
58
 
54
59
  ## Entity Definition
@@ -235,7 +240,7 @@ export const subscribables = {
235
240
 
236
241
  root: {
237
242
  entity: 'venues',
238
- key: 'venue_id' // Maps param to entity PK
243
+ key: 'venue_id' // Must match a param name
239
244
  },
240
245
 
241
246
  includes: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dzql",
3
- "version": "0.6.29",
3
+ "version": "0.6.30",
4
4
  "description": "Database-first real-time framework with TypeScript support",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,6 +11,17 @@
11
11
  import type { SubscribableIR, IncludeIR, EntityIR } from "../../shared/ir.js";
12
12
 
13
13
  export function generateSubscribableSQL(name: string, sub: SubscribableIR, entities: Record<string, EntityIR> = {}): string {
14
+ // Validate: if root.key is set, it must exist in params or start with '@'
15
+ const rootKey = sub.root.key;
16
+ const paramNames = Object.keys(sub.params);
17
+ if (rootKey && !rootKey.startsWith('@') && !paramNames.includes(rootKey)) {
18
+ throw new Error(
19
+ `[Compiler] Subscribable '${name}' has root.key='${rootKey}' but no matching param. ` +
20
+ `Either add '${rootKey}' to params, use '@user_id' for user-scoped subscribables, ` +
21
+ `or remove the key for list subscribables.`
22
+ );
23
+ }
24
+
14
25
  const sections: string[] = [];
15
26
 
16
27
  sections.push(generateHeader(name, sub));
@@ -69,6 +80,13 @@ $$;`;
69
80
  ` v_${p} := (p_params->>'${p}')::${sub.params[p]};`
70
81
  ).join('\n');
71
82
 
83
+ // Determine how to reference the root key in the WHERE clause
84
+ // Validation already done at top of generateSubscribableSQL
85
+ const rootKey = sub.root.key;
86
+ const rootKeyExpr = rootKey.startsWith('@')
87
+ ? `p_${rootKey.slice(1)}` // @user_id -> p_user_id
88
+ : `v_${rootKey}`; // venue_id -> v_venue_id
89
+
72
90
  return `CREATE OR REPLACE FUNCTION dzql_v2.${name}_can_subscribe(
73
91
  p_user_id INT,
74
92
  p_params JSONB
@@ -88,7 +106,7 @@ ${paramExtracts}
88
106
  -- Fetch root entity
89
107
  SELECT * INTO v_root
90
108
  FROM ${sub.root.entity}
91
- WHERE id = v_${sub.root.key};
109
+ WHERE id = ${rootKeyExpr};
92
110
 
93
111
  IF NOT FOUND THEN
94
112
  RETURN FALSE;
@@ -220,9 +238,9 @@ function generateGetFunction(name: string, sub: SubscribableIR, entities: Record
220
238
  ` v_${p} := (p_params->>'${p}')::${sub.params[p]};`
221
239
  ).join('\n');
222
240
 
223
- // Handle special @user_id root key
241
+ // Handle special @ prefixed root keys (e.g., @user_id -> p_user_id)
224
242
  const rootKey = sub.root.key;
225
- const isUserIdRoot = rootKey === '@user_id';
243
+ const isSpecialKey = rootKey.startsWith('@');
226
244
  const isList = !rootKey; // No key means it's a list subscribable
227
245
 
228
246
  // Build root select expression excluding hidden fields
@@ -242,7 +260,7 @@ function generateGetFunction(name: string, sub: SubscribableIR, entities: Record
242
260
  // Build WHERE clause based on root filter and key
243
261
  const whereConditions: string[] = [];
244
262
  if (rootKey) {
245
- const rootWhereValue = isUserIdRoot ? 'p_user_id' : `v_${rootKey}`;
263
+ const rootWhereValue = isSpecialKey ? `p_${rootKey.slice(1)}` : `v_${rootKey}`;
246
264
  whereConditions.push(`root.id = ${rootWhereValue}`);
247
265
  }
248
266
  if (sub.root.filter) {