dzql 0.5.8 → 0.5.10

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.8",
3
+ "version": "0.5.10",
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",
@@ -238,13 +238,22 @@ $$ LANGUAGE plpgsql STABLE SECURITY DEFINER;`;
238
238
  // Build relation subqueries
239
239
  const relationSelects = this._generateRelationSelects();
240
240
 
241
+ // Build schema with path mapping and scope tables (baked in at compile time)
242
+ const pathMapping = this.buildPathMapping();
243
+ const scopeTables = this.extractScopeTables();
244
+ const schemaJson = JSON.stringify({
245
+ root: this.rootEntity,
246
+ paths: pathMapping,
247
+ scopeTables: scopeTables
248
+ });
249
+
241
250
  return `CREATE OR REPLACE FUNCTION get_${this.name}(
242
251
  p_params JSONB,
243
252
  p_user_id INT
244
253
  ) RETURNS JSONB AS $$
245
254
  DECLARE
246
255
  ${paramDeclarations}
247
- v_result JSONB;
256
+ v_data JSONB;
248
257
  BEGIN
249
258
  -- Extract parameters
250
259
  ${paramExtractions}
@@ -258,11 +267,15 @@ ${paramExtractions}
258
267
  SELECT jsonb_build_object(
259
268
  '${this.rootEntity}', row_to_json(root.*)${relationSelects}
260
269
  )
261
- INTO v_result
270
+ INTO v_data
262
271
  FROM ${this.rootEntity} root
263
272
  WHERE ${rootFilter};
264
273
 
265
- RETURN v_result;
274
+ -- Return data with embedded schema for atomic updates
275
+ RETURN jsonb_build_object(
276
+ 'data', v_data,
277
+ 'schema', '${schemaJson}'::jsonb
278
+ );
266
279
  END;
267
280
  $$ LANGUAGE plpgsql SECURITY DEFINER;`;
268
281
  }
@@ -221,50 +221,55 @@ export function getAllSubscriptions() {
221
221
  * @returns {Promise<{scopeTables: string[], pathMapping: object, rootEntity: string, relations: object}>}
222
222
  */
223
223
  export async function getSubscribableMetadata(subscribableName, sql) {
224
- // Check cache first
224
+ // Check cache first (compiled mode populates this from embedded schema)
225
225
  if (subscribableMetadataCache.has(subscribableName)) {
226
226
  return subscribableMetadataCache.get(subscribableName);
227
227
  }
228
228
 
229
- // Fetch from database
230
- const result = await sql`
231
- SELECT scope_tables, root_entity, relations
232
- FROM dzql.subscribables
233
- WHERE name = ${subscribableName}
234
- `;
235
-
236
- if (!result || result.length === 0) {
237
- // Return empty metadata if subscribable not found
238
- const emptyMetadata = {
239
- scopeTables: [],
240
- pathMapping: {},
241
- rootEntity: null,
242
- relations: {}
243
- };
244
- return emptyMetadata;
245
- }
229
+ // Interpreted mode: fetch from database table
230
+ try {
231
+ const result = await sql`
232
+ SELECT scope_tables, root_entity, relations
233
+ FROM dzql.subscribables
234
+ WHERE name = ${subscribableName}
235
+ `;
246
236
 
247
- const { scope_tables, root_entity, relations } = result[0];
237
+ if (result && result.length > 0) {
238
+ const { scope_tables, root_entity, relations } = result[0];
248
239
 
249
- // Build path mapping from relations
250
- const pathMapping = buildPathMappingFromRelations(root_entity, relations);
240
+ // Build path mapping from relations
241
+ const pathMapping = buildPathMappingFromRelations(root_entity, relations);
251
242
 
252
- const metadata = {
253
- scopeTables: scope_tables || [],
254
- pathMapping,
255
- rootEntity: root_entity,
256
- relations: relations || {}
257
- };
243
+ const metadata = {
244
+ scopeTables: scope_tables || [],
245
+ pathMapping,
246
+ rootEntity: root_entity,
247
+ relations: relations || {}
248
+ };
258
249
 
259
- // Cache for future use
260
- subscribableMetadataCache.set(subscribableName, metadata);
250
+ // Cache for future use
251
+ subscribableMetadataCache.set(subscribableName, metadata);
261
252
 
262
- wsLogger.debug(`Cached metadata for subscribable ${subscribableName}:`, {
263
- scopeTables: metadata.scopeTables,
264
- pathMapping: metadata.pathMapping
265
- });
253
+ wsLogger.debug(`Cached metadata for subscribable ${subscribableName}:`, {
254
+ scopeTables: metadata.scopeTables,
255
+ pathMapping: metadata.pathMapping
256
+ });
266
257
 
267
- return metadata;
258
+ return metadata;
259
+ }
260
+ } catch (e) {
261
+ // Table doesn't exist or query failed
262
+ wsLogger.debug(`Failed to fetch metadata for ${subscribableName}: ${e.message}`);
263
+ }
264
+
265
+ // Return empty metadata if nothing found (compiled mode without prior subscribe)
266
+ const emptyMetadata = {
267
+ scopeTables: [],
268
+ pathMapping: {},
269
+ rootEntity: null,
270
+ relations: {}
271
+ };
272
+ return emptyMetadata;
268
273
  }
269
274
 
270
275
  /**
@@ -322,6 +327,18 @@ export function clearSubscribableMetadataCache(subscribableName = null) {
322
327
  }
323
328
  }
324
329
 
330
+ /**
331
+ * Cache subscribable metadata (used by compiled mode to store embedded schema)
332
+ * @param {string} subscribableName - Name of the subscribable
333
+ * @param {object} metadata - Metadata to cache
334
+ */
335
+ export function cacheSubscribableMetadata(subscribableName, metadata) {
336
+ subscribableMetadataCache.set(subscribableName, metadata);
337
+ wsLogger.debug(`Cached compiled metadata for ${subscribableName}:`, {
338
+ scopeTables: metadata.scopeTables
339
+ });
340
+ }
341
+
325
342
  /**
326
343
  * Get scope tables for a subscribable (convenience function)
327
344
  * @param {string} subscribableName - Name of the subscribable
package/src/server/ws.js CHANGED
@@ -12,7 +12,8 @@ import {
12
12
  unregisterSubscription,
13
13
  unregisterSubscriptionByParams,
14
14
  removeConnectionSubscriptions,
15
- getSubscribableMetadata
15
+ getSubscribableMetadata,
16
+ cacheSubscribableMetadata
16
17
  } from "./subscriptions.js";
17
18
 
18
19
  // Environment configuration
@@ -322,14 +323,37 @@ export function createRPCHandler(customHandlers = {}) {
322
323
  try {
323
324
  // Execute initial query (this also checks permissions)
324
325
  const queryResult = await sql.unsafe(
325
- `SELECT get_${subscribableName}($1, $2) as data`,
326
+ `SELECT get_${subscribableName}($1, $2) as result`,
326
327
  [params, ws.data.user_id]
327
328
  );
328
329
 
329
- const data = queryResult[0]?.data;
330
-
331
- // Get subscribable metadata for schema (path mapping for atomic updates)
332
- const metadata = await getSubscribableMetadata(subscribableName, sql);
330
+ const queryData = queryResult[0]?.result;
331
+
332
+ // Check if compiled function returned embedded schema
333
+ // Compiled: { data, schema } | Interpreted: just the data object
334
+ let data, schema;
335
+ if (queryData && queryData.schema && queryData.data !== undefined) {
336
+ // Compiled mode - schema is embedded (includes scopeTables)
337
+ data = queryData.data;
338
+ schema = queryData.schema;
339
+ // Cache scopeTables for event filtering
340
+ if (schema.scopeTables) {
341
+ cacheSubscribableMetadata(subscribableName, {
342
+ scopeTables: schema.scopeTables,
343
+ pathMapping: schema.paths,
344
+ rootEntity: schema.root,
345
+ relations: {}
346
+ });
347
+ }
348
+ } else {
349
+ // Interpreted mode - fetch schema from metadata table
350
+ data = queryData;
351
+ const metadata = await getSubscribableMetadata(subscribableName, sql);
352
+ schema = {
353
+ root: metadata.rootEntity,
354
+ paths: metadata.pathMapping
355
+ };
356
+ }
333
357
 
334
358
  // Register subscription in memory
335
359
  const subscriptionId = registerSubscription(
@@ -343,11 +367,7 @@ export function createRPCHandler(customHandlers = {}) {
343
367
  const result = {
344
368
  subscription_id: subscriptionId,
345
369
  data,
346
- // Include schema for atomic update support
347
- schema: {
348
- root: metadata.rootEntity,
349
- paths: metadata.pathMapping
350
- }
370
+ schema
351
371
  };
352
372
 
353
373
  wsLogger.response(method, result, Date.now() - startTime);