dzql 0.6.15 → 0.6.16

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.15",
3
+ "version": "0.6.16",
4
4
  "description": "Database-first real-time framework with TypeScript support",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,6 @@
1
1
  import { compilePermission } from "../compiler/permissions.js";
2
2
  import { compileGraphRules } from "../compiler/graph_rules.js";
3
- import type { EntityIR, ManyToManyIR } from "../../shared/ir.js";
3
+ import type { EntityIR, ManyToManyIR, IncludeIR } from "../../shared/ir.js";
4
4
 
5
5
  /** Column info from EntityIR */
6
6
  interface ColumnInfo {
@@ -313,6 +313,32 @@ export function generateSaveFunction(name: string, entityIR: EntityIR): string {
313
313
  return sql;
314
314
  }).join('\n');
315
315
 
316
+ // FK expansion (add related objects to output for real-time events)
317
+ // Only expand direct FKs (where key_id column exists), not reverse FKs (child arrays)
318
+ const includes: Record<string, IncludeIR> = entityIR.includes || {};
319
+ const includeKeys = Object.keys(includes);
320
+ const fkExpansion = includeKeys.map(key => {
321
+ const config: IncludeIR = includes[key];
322
+ const targetEntity = config.entity;
323
+ const fkField = `${key}_id`; // Convention: author -> author_id
324
+
325
+ // Only expand if this is a direct FK (key_id column exists)
326
+ const hasFkColumn = entityIR.columns.some((c: ColumnInfo) => c.name === fkField);
327
+
328
+ if (hasFkColumn) {
329
+ // Direct FK: single object expansion (e.g., author_id -> author object)
330
+ return `
331
+ -- FK: Add ${key} to output (from ${fkField})
332
+ IF (v_result->>'${fkField}') IS NOT NULL THEN
333
+ v_result := v_result || jsonb_build_object('${key}',
334
+ (SELECT to_jsonb(t.*) FROM ${targetEntity} t WHERE t.id = (v_result->>'${fkField}')::int));
335
+ END IF;`;
336
+ }
337
+ // Skip reverse FKs - they would require querying child tables which may not exist
338
+ // and are not needed for the primary use case of expanding the saved record
339
+ return '';
340
+ }).filter(s => s).join('\n');
341
+
316
342
  return `
317
343
  CREATE OR REPLACE FUNCTION dzql_v2.save_${name}(p_user_id int, p_data jsonb)
318
344
  RETURNS jsonb
@@ -365,6 +391,7 @@ ${m2mExtraction}
365
391
  END IF;
366
392
  ${m2mSync}
367
393
  ${m2mExpansion}
394
+ ${fkExpansion}
368
395
 
369
396
  -- Resolve notification recipients
370
397
  v_notify_users := dzql_v2.${name}_notify_users(p_user_id, v_result);
@@ -175,6 +175,9 @@ export function generateIR(domain: DomainConfig): DomainIR {
175
175
  };
176
176
  }
177
177
 
178
+ // Parse includes (FK expansions)
179
+ const includes = parseIncludes(config.includes as Record<string, string | IncludeConfig> | undefined);
180
+
178
181
  entities[name] = {
179
182
  name,
180
183
  table: name,
@@ -187,6 +190,7 @@ export function generateIR(domain: DomainConfig): DomainIR {
187
190
  fieldDefaults: config.fieldDefaults || {},
188
191
  permissions,
189
192
  relationships: {},
193
+ includes,
190
194
  manyToMany,
191
195
  graphRules: {
192
196
  onCreate: onCreateRules,
package/src/shared/ir.ts CHANGED
@@ -130,6 +130,7 @@ export interface EntityIR {
130
130
  delete: string[];
131
131
  };
132
132
  relationships: Record<string, RelationshipIR>;
133
+ includes: Record<string, IncludeIR>; // FK expansions (e.g., author: users)
133
134
  manyToMany: Record<string, ManyToManyIR>;
134
135
  graphRules: {
135
136
  onCreate: GraphRuleIR[];