v1.1.0

🔗 Include Functionality Demo

What is include()?

The include() function allows you to embed one component inside another. This is perfect for reusing code, creating modular components, and building complex pages from simple parts.

1. Basic Include - Simple Component

<% await include('/demo/components/simple-info.html') %>

Included Component Output:

Simple Info Component

This is a reusable component!

It was included using include('/components/simple-info.html')

Rendered at: 27/12/2025, 8:27:14 am

Request path: /demo/components/simple-info.html

✓ This component was included successfully! Notice it has access to the same $req, $session, and other global context.

2. Include with Parameters

<% await include('/demo/components/user-card.html', { name: 'John Doe', role: 'Administrator', email: '[email protected]' }) %>

Included Component Output:

J

John Doe

ADMINISTRATOR

[email protected]

✓ Parameters were passed successfully! The component received the custom data.

3. Multiple Includes - Different Parameters

You can include the same component multiple times with different parameters:

<% await include('/demo/components/user-card.html', { name: 'Alice', role: 'Developer' }) %> <% await include('/demo/components/user-card.html', { name: 'Bob', role: 'Designer' }) %> <% await include('/demo/components/user-card.html', { name: 'Charlie', role: 'Manager' }) %>

Included Components Output:

A

Alice

DEVELOPER

[email protected]

B

Bob

DESIGNER

[email protected]

C

Charlie

MANAGER

[email protected]

✓ Same component included 3 times with different data!

4. Include with Database Access

Included components have full access to $db, $cache, and all other global context:

<% await include('/demo/components/db-summary.html') %>

Included Component Output:

StoneJS Framework - Database Demo

Database Integration Demo

This page demonstrates how to connect to PostgreSQL and query data using the $db object.

✓ Database Connected!

Database: ddata
Host: pgsql-prod01.postgres.database.azure.com:5432
Query Time: 45ms
Total Records: 400713

Query Results

Showing 61 - 70 of 400713 records

RID Operation Table Name User ID Description Created
400658 INSERT chat_sessions ddai_access_rw N/A 10/11/2025, 9:24:12 am
400657 INSERT chat_sessions ddai_access_rw N/A 10/11/2025, 9:20:44 am
400656 INSERT chat_sessions ddai_access_rw N/A 10/11/2025, 9:12:39 am
400655 INSERT chat_sessions ddai_access_rw N/A 10/11/2025, 9:06:40 am
400654 INSERT chat_sessions ddai_access_rw N/A 10/11/2025, 9:05:12 am
400653 INSERT chat_sessions ddai_access_rw N/A 10/11/2025, 9:04:45 am
400652 INSERT chat_sessions ddai_access_rw N/A 10/11/2025, 9:04:29 am
400651 INSERT chat_sessions ddai_access_rw N/A 10/11/2025, 9:04:25 am
400650 INSERT chat_sessions ddai_access_rw N/A 10/11/2025, 9:04:08 am
400649 INSERT chat_sessions ddai_access_rw N/A 10/11/2025, 9:02:50 am
← Previous Page 7 of 40072 Next →

Database API Reference

The $db object is available in all components and provides two main methods for database operations.

1. $db.query() - Standard Queries

Use for regular database operations without row-level security.

Single Query

// Query returns rows directly (no .rows wrapper) const users = await $db.query('SELECT * FROM users LIMIT 10'); // With parameters (prevents SQL injection) const user = await $db.query('SELECT * FROM users WHERE id = $1', [userId]);

Multiple Queries (Array Syntax)

Execute multiple queries on the same connection. Returns array of results.

const results = await $db.query([ { sql: 'SELECT * FROM users WHERE active = $1', params: [true] }, { sql: 'SELECT * FROM orders WHERE status = $1', params: ['pending'] }, 'SELECT COUNT(*) FROM audit_log' // String format (no params) ]); const activeUsers = results[0]; const pendingOrders = results[1]; const auditCount = results[2][0].count;

Transactions

Wrap operations in BEGIN/COMMIT. All succeed or all fail together.

try { const results = await $db.query([ 'BEGIN', { sql: 'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id', params: ['John Doe', '[email protected]'] }, { sql: 'INSERT INTO audit_log (action, userid) VALUES ($1, $2)', params: ['user_created', 'admin'] }, 'COMMIT' ]); const newUserId = results[1][0].id; } catch (err) { // Transaction automatically rolled back on error console.error('Transaction failed:', err); }

2. $db.secureQuery() - Row-Level Security

Use for tables with PostgreSQL RLS enabled. Automatically manages session variables in a transaction.

Single Session Variable

const logs = await $db.secureQuery( 'SELECT * FROM audit_log LIMIT 10', [], 'session.accountcode', $session.accountcode );

Multiple Session Variables

const logs = await $db.secureQuery( 'SELECT * FROM audit_log WHERE userid = $1', ['user123'], { 'session.accountcode': $session.accountcode, 'session.userid': $session.userId, 'session.role': $session.userRole } );

Multiple Queries with RLS

const results = await $db.secureQuery( [ { sql: 'SELECT * FROM orders WHERE status = $1', params: ['pending'] }, { sql: 'SELECT * FROM invoices WHERE paid = $1', params: [false] }, 'SELECT * FROM customers' ], null, // params not used when first arg is array { 'session.accountcode': $session.accountcode } ); const pendingOrders = results[0]; const unpaidInvoices = results[1]; const customers = results[2];

How secureQuery() Works

Automatic Session Variable Management:

  1. Gets a dedicated connection from the pool
  2. Starts a transaction (BEGIN)
  3. Sets session variable(s) with SET LOCAL
  4. Executes your query/queries
  5. Commits the transaction (automatically clears SET LOCAL variables)
  6. Releases the connection back to the pool

Why this matters: Session variables are scoped to the transaction, preventing variable leakage between requests.

Setting up Row-Level Security in PostgreSQL

-- Enable RLS on a table ALTER TABLE audit_log ENABLE ROW LEVEL SECURITY; -- Create a policy using the session variable CREATE POLICY account_isolation ON audit_log FOR ALL USING ( accountcode = current_setting('session.accountcode', true) ); -- Grant access to your application role GRANT SELECT, INSERT, UPDATE, DELETE ON audit_log TO app_role;

âš ī¸ Important: Regular $db.query() doesn't set session variables. Always use $db.secureQuery() for RLS-enabled tables, or RLS policies will block all rows.

Configuration

Database configuration is loaded from your .env file:

# PostgreSQL Configuration DB_TYPE=postgresql DB_HOST=localhost DB_PORT=5432 DB_USER=your_username DB_PASSWORD=your_password DB_NAME=your_database DB_SSL=true # Optional: Enable SSL connections # Connection Pool Settings (optional) # DB_POOL_MIN=2 # DB_POOL_MAX=10 # DB_POOL_IDLE_TIMEOUT=30000

Return Value Format

Important: StoneJS $db.query() returns rows directly, not wrapped in a .rows property like the native pg library.

Library Return Format
pg (native) result.rows
$db.query() result (array directly)

Best Practices

  • Always use parameterized queries - Use $1, $2 placeholders to prevent SQL injection
  • Use secureQuery() for RLS tables - Automatically manages session variables
  • Use array syntax for related queries - Ensures they run on the same connection
  • Use transactions for atomic operations - Wrap in BEGIN/COMMIT
  • Handle errors gracefully - Use try/catch blocks
  • Combine with caching - Use $cache.remember() for expensive queries

Example: Caching Database Results

// Cache results for 5 minutes (300 seconds) const logs = await $cache.remember('audit-logs-recent', 300, async () => { return await $db.query( 'SELECT * FROM audit_log ORDER BY cdate DESC LIMIT 100' ); });

Quick Reference Table

Feature $db.query() $db.secureQuery()
Single query ✓ Yes ✓ Yes
Multiple queries ✓ Array syntax ✓ Array syntax
Transactions ✓ Manual BEGIN/COMMIT ✓ Automatic wrapper
Session variables ✗ No ✓ Automatic SET LOCAL
Use case Standard queries RLS-enabled tables
Connection handling Automatic pool management Dedicated connection per call

Session Stats: You've viewed this page 1 time(s).

✓ The included component queried the database successfully!

5. Include with Session Access

Included components can read and write session data:

<% await include('/demo/components/session-info.html') %>

Included Component Output:

Session Information Component

Session ID: CmWcinP2a1OL4F1mZ1wIpb6cAXx3J8lh
Visit Count: 0
User Name: Not set

â„šī¸ This component has full access to the global $session object

✓ The included component accessed session data!

6. đŸ”Ĩ NEW: Dhandler Resolution

Major Feature: The include() function now supports dhandler resolution, just like HTTP requests!

When you include a non-existent file path, StoneJS will search up the directory tree for a dhandler to handle it.

How It Works

Let's include a non-existent CSS file. StoneJS will find the /demo/css/dhandler and use it:

<%- await include('/demo/css/imaginary-file-12345.css') %>

What happens:

  1. StoneJS looks for /demo/css/imaginary-file-12345.css → not found
  2. Searches up the tree for a dhandler → finds /demo/css/dhandler
  3. Executes the dhandler with $context.dhandlerPath = 'imaginary-file-12345.css'
  4. Returns the rendered output

Live Demo: CSS Dhandler

Including: /demo/css/test-1766784434954.css

This file doesn't exist, but the dhandler will handle it!

✅ Success! Received 21636 characters of CSS from the dhandler.

First 500 characters of output:

/* ============================================================================ STONEJS DEMO - MODERN CORPORATE DESIGN SYSTEM - 1.1.0 ============================================================================ A clean, blocky, corporate design system with: - Strong geometric shapes and clear boundaries - Professional color palette - Consistent spacing using 8px grid system - Accessible contrast ratios - Responsive design patterns ==================================...

Comparison: HTTP vs Include

Both methods now work identically:

Method Example Behavior
HTTP Request GET /demo/css/abc123.css ✅ Dhandler handles it
Include Function <%- await include('/demo/css/abc123.css') %> ✅ Dhandler handles it

Use Cases for Dhandler Includes

🎨
Dynamic CSS
Include CSS with cache-busting: /css/style.<%= hash %>.css
đŸ–ŧī¸
Image Processing
Generate resized images: /images/photo-400x300.jpg
📄
Dynamic Content
Include content based on patterns: /posts/<%= slug %>.html
🔧
Dynamic JS
Include versioned scripts: /js/app.<%= version %>.js

âš ī¸ Important: Dhandler Design for Includes

When creating dhandlers that will be used with include():

// ✅ GOOD - Works with both HTTP and include() // File: /demo/css/dhandler /* CSS content here */ body { color: #333; } // ❌ BAD - Only works with HTTP, not include() // File: /demo/api/dhandler <%%js> $res.send('content'); </js%%>

7. Include Paths - Absolute vs Relative

Path Types:

Absolute Path: Starts with / and is relative to the pages root directory

<% await include('/demo/components/simple-info.html') %>

Relative Path: Relative to the current component's directory

<% await include('../demo/components/simple-info.html') %> <% await include('./components/simple-info.html') %>

📚 Key Points

How Include Works:

Best Practices:

Common Use Cases: