npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@semantq/ql

v1.1.12

Published

CRUD abstraction module for SemantqQL

Readme

smQL

CRUD Abstraction for Semantq Full Stack Apps

smQL is a lightweight, robust, and reusable JavaScript HTTP client class that simplifies CRUD (Create, Read, Update, Delete) operations using standard fetch API calls. It includes complementary Form and Notification utilities for complete frontend API interaction management.

Installation

npm install @semantq/ql

Import into your project:

// Import all components
import { smQL, Form, Notification } from '@semantq/ql';

// Or import selectively
import { smQL } from '@semantq/ql';

Core Features

  • smQL: HTTP client with built-in token management and logging
  • Form: Form data capture and submission handler
  • Notification: Elegant notification system with multiple types

smQL HTTP Client

Basic Usage

First, create a client instance:

const api = new smQL(`${baseOrigin}`); //
// OR if outside the context of Semantq full stack
const api = new smQL(`https://api.example/com`);
const response = await api.get('/students');

**Example API response:**

```json
{
  "_status": 200,
  "_ok": true,
  "data": {
    "0": { "id": 1, "name": "Alice" },
    "1": { "id": 2, "name": "Bob" }
  },
  "meta": {
    "total": 2,
    "page": 1
  }
}

Note:
In the context of a Semantq Full Stack app, when you create a route using:

semantq make:route dashboard/students -c

the generated route file (@page.smq) includes full CRUD and API boilerplate, including:

const api = new smQL(`${baseOrigin}`);

The goal of Semantq is to save you time, streamline development, and optimize your workflow.

B. Extract Data with the Same Client

const students = api.getData(response);

Extracted data:

[
  { "id": 1, "name": "Alice" },
  { "id": 2, "name": "Bob" }
]

Note: Using api.getData(response) is only needed when working with lists.
For single-record responses, you can use the response object directly.

// Make the API call (fetching a single record)
const response = await api.get('/students/1');
// or using a variable ID
const response = await api.get(`/students/${studentId}`);

// No need to call `getData`; the response can be used as-is
// Example response structure:
{
  "id": 1,
  "name": "Alice"
}

Static One-Off Calls

const response = await smQL.fetch(
  'https://api.example.com', 
  '/users',
  'GET',
  null,
  { 'X-Custom-Header': 'value' }
);

Instance-Based Usage

const api = new smQL('https://api.example.com', {
  'X-API-Version': '1.0'
}, { log: true });

// Set authentication token
api.setToken('your_jwt_token_here');

// Make requests
const users = await api.get('/users');
const newUser = await api.post('/users', { name: 'John' });

Configuration Options

Constructor Parameters

const api = new smQL(baseURL, defaultHeaders, options);

| Parameter | Type | Description | |-----------|------|-------------| | baseURL | string | Base API URL | | defaultHeaders | object | Default headers (default: {'Content-Type': 'application/json'}) | | options | object | Additional options: |

Options:

{
  log: true, // Enable/disable request logging (default: true)
}

CRUD Methods

All methods return a response object with:

  • _status: HTTP status code
  • _ok: Boolean indicating success
  • Response data

** Handling API Responses**

When you receive a response from an API call using smQL, the data payload may contain more than just your records, such as metadata or status information. For requests that return a list of records (e.g., /products, /users), smQL provides a utility method, getData, to cleanly extract the array of objects.

1. Using an Established Client Instance

The most common and recommended approach is to use your existing smQL client instance to process the response. This is the cleanest and most developer-friendly method, as you don't need to create a new object just to process the data.

// A. Make the API call using your pre-configured client
const api = new smQL('https://api.example.com');
const response = await api.get('/students');

/*
Typical Data Structure after API Call (`response`):

{
  "0": { "id": 1, "name": "Alice" },
  "1": { "id": 2, "name": "Bob" },
  "_status": 200,
  "_ok": true
}
*/

// B. Use the same client instance to extract the data
const students = api.getData(response);

/*
Typical Data Structure after Extraction (`students`):

[
  { "id": 1, "name": "Alice" },
  { "id": 2, "name": "Bob" }
]
*/

2. Creating a New Client Instance

Alternatively, you can create a new, lightweight smQL instance specifically for a single data extraction task. This is useful if you don't have an existing client handle readily available in your code's scope.

// A. Make the API call (e.g., using another method or static fetch)
const response = await smQL.fetch('https://api.example.com', '/students');

// B. Create a new instance to process the response
const client = new smQL();
const students = client.getData(response);

console.log(students);

3. Manual Data Extraction (Advanced)

For developers who prefer to handle the data processing manually, you can directly filter the response object to get the array of records. This method is an alternative to using the getData utility. It is necessary when you are not fetching a list of records but a single record like product/:id. In such cases, the getData method will return an empty array.

const response = await api.get('/students');

// Manually filter the object to extract the array of records
const students = Object.values(response).filter(
    item => typeof item === 'object' && item !== null && !Array.isArray(item)
);

console.log(students);

When to Use getData

The getData method is essential for handling API responses that are structured with extra properties beyond the main data array. It intelligently filters out metadata, status codes, and other non-record objects, returning a clean, usable array.

If your API call returns a single record (e.g., /products/123), the getData method is not needed, as the response object itself contains the record and can be used directly.

GET Request

const response = await api.get('/users', {
  'Cache-Control': 'no-cache'
});

POST Request

const response = await api.post('/users', {
  name: 'Alice',
  email: '[email protected]'
});

PUT Request

const response = await api.put('/users/123', {
  name: 'Alice Smith'
});

PATCH Request

const response = await api.patch('/users/123', {
  email: '[email protected]'
});

DELETE Request

const response = await api.delete('/users/123');

Logging System

smQL provides detailed request logging when enabled:

// Enable logging (default)
const api = new smQL('https://api.example.com', {}, { log: true });

// Disable logging
const silentApi = new smQL('https://api.example.com', {}, { log: false });

Log Format:

[smQL] Request to /users returned status 200 {
  _status: 200,
  _ok: true,
  data: [...]
}

Error Handling

smQL throws errors for:

  • Network failures
  • Invalid responses
  • Parsing errors
try {
  const response = await api.get('/nonexistent');
} catch (error) {
  console.error('API Error:', error);
  Notification.show({
    type: 'error',
    message: 'Failed to load data'
  });
}

Form Handler

The Form class simplifies form data capture and handling.

Basic Usage

Form Submission Example (without notifications)

// Initialize the form handler
const studentForm = new Form('student-form'); // student-form is the id of the form element

studentForm.form.addEventListener('form:captured', async (event) => {
  try {
    const payload = event.detail;
    await api.post('/students', payload);
    //console.log('Student added:', payload);
    // Optionally reset the form
    // event.target.reset();
  } catch (error) {
    console.error('Failed to add student:', error);
  }
});

This version emphasizes simplicity — just handle the submission and API call.

Form Submission Example with Notifications and Custom Branding Colors

const studentForm = new Form('student-form');

studentForm.form.addEventListener('form:captured', async (event) => {
  const payload = event.detail;

  try {
    await api.post('/students', payload);

    Notification.show({
      type: 'success',
      message: 'Student added successfully!',
      duration: 3000,
      successColor: '#28a745', // Custom green
      themeColor: '#007bff'    // Global theme for all types
    });

    console.log('Student added:', payload);
    // event.target.reset();

  } catch (error) {
    Notification.show({
      type: 'error',
      message: `Student creation failed: ${error.message}`,
      duration: 5000,
      errorColor: '#dc3545',   // Custom red
      themeColor: '#007bff'
    });

    console.error('Student creation failed:', error);
  }
});

Declarative Form Submission Using a Form Class with onCaptured Callback

const form = new Form('userForm', 'submit', {
  debug: true,
  onCaptured: (data) => {
    api.post('/users', data)
      .then(() => Notification.show({
        type: 'success',
        message: 'User created!'
      }));
  }
});

Advanced Features

Multi-Select Support

Automatically handles <select multiple> elements.

Array Values

Converts repeated fields into arrays automatically.

Event System

document.getElementById('userForm').addEventListener('form:captured', (e) => {
  console.log('Form data:', e.detail);
});

Methods

getData()

Returns the captured form data:

const form = new Form('userForm');
// ... after form submission
console.log(form.getData());

Notification System

Display user notifications with multiple types, customizable colors, and flexible options.

Basic Usage

Notification.show({
  type: 'success',
  message: 'Operation completed successfully!',
  duration: 5000
});

Notification Types

  • success: Green notification with checkmark (✓)
  • error: Red notification with warning icon (⚠)
  • warning: Orange notification with warning icon (⚠)

Configuration Options

{
  type: 'success',           // 'success', 'error', or 'warning'
  message: 'Message text',   // Notification content (supports HTML)
  duration: 5000,            // Auto-close in milliseconds (0 = persistent)
  closeable: true,           // Show close button
  successColor: '#4CAF50',   // Optional: Custom success color
  errorColor: '#F44336',     // Optional: Custom error color  
  warningColor: '#FF9800',   // Optional: Custom warning color
  themeColor: '#2196F3'      // Optional: Global theme color for all types
}

Color Customization

Type-Specific Colors

Override colors for specific notification types:

// Custom success color (teal)
Notification.show({
  type: 'success',
  message: 'QR Codes emailed successfully!',
  duration: 3000,
  successColor: '#009688' // Overrides default green
});

// Custom error color (purple)
Notification.show({
  type: 'error',
  message: 'Something went wrong!',
  duration: 3000,
  errorColor: '#9C27B0' // Overrides default red
});

// Custom warning color (amber)
Notification.show({
  type: 'warning',
  message: 'Please check your input',
  duration: 3000,
  warningColor: '#FFC107' // Overrides default orange
});

Global Theme Color

Apply a consistent color across all notification types:

// All notifications will use blue theme
Notification.show({
  type: 'success',
  message: 'Operation completed!',
  duration: 3000,
  themeColor: '#2196F3' // Applies to success, error, and warning
});

Color Priority System

The notification system follows this priority order:

  1. Type-specific color (successColor, errorColor, warningColor)
  2. Global theme color (themeColor)
  3. Default color (if no custom colors provided)
// Example: Only warningColor will be used (highest priority)
Notification.show({
  type: 'warning',
  message: 'Please check your API call or server',
  duration: 3000,
  warningColor: '#FFC107',    // USED (type-specific)
  successColor: '#009688',    // Ignored (wrong type)
  errorColor: '#E91E63'       // Ignored (wrong type)
});

Advanced Features

Stacked Notifications

Multiple notifications automatically stack vertically with smooth animations.

Custom Icons and HTML

Include custom icons or HTML in messages:

Notification.show({
  type: 'success',
  message: 'Custom icon <i class="fa fa-check-circle"></i> Success!'
});

Persistent Notifications

Set duration: 0 for notifications that require manual dismissal:

Notification.show({
  type: 'error',
  message: 'Critical system error!',
  duration: 0, // Requires user to click close
  closeable: true
});

Auto-Close Control

// Auto-close after 3 seconds
Notification.show({
  type: 'success', 
  message: 'Temporary message',
  duration: 3000
});

// Manual close only
Notification.show({
  type: 'warning',
  message: 'Important notice',
  duration: 0,
  closeable: true
});

Default Values

const defaults = {
  type: 'success',
  message: 'Operation completed successfully',
  duration: 5000,
  closeable: true,
  successColor: null,    // Default: #4CAF50
  errorColor: null,      // Default: #F44336  
  warningColor: null,    // Default: #FF9800
  themeColor: null       // No global theme by default
}

The notification system is fully backward-compatible - existing code will continue to work without any changes.

Comprehensive Examples

Complete CRUD Workflow

// 1. Initialize API client
const api = new smQL('https://api.example.com');

// 2. Set up form handler
new Form('productForm', {
  onCaptured: async (data) => {
    try {
      // 3. Submit data
      const response = await api.post('/products', data);
      
      // 4. Show success notification with custom color
      Notification.show({
        type: 'success',
        message: 'Product created successfully!',
        duration: 3000,
        successColor: '#009688' // Teal success color
      });
      
      // 5. Refresh product list
      const products = await api.get('/products');
      renderProductList(products);
      
    } catch (error) {
      // Show error notification with custom color
      Notification.show({
        type: 'error',
        message: 'Failed to create product: ' + error.message,
        duration: 5000,
        errorColor: '#E91E63' // Pink error color
      });
    }
  }
});

// Advanced CRUD examples with different notification styles

// CREATE with persistent notification
async function createProduct(productData) {
  try {
    await api.post('/products', productData);
    
    Notification.show({
      type: 'success',
      message: 'Product added to database',
      duration: 0, // Persistent - requires manual close
      closeable: true,
      successColor: '#4CAF50'
    });
    
  } catch (error) {
    Notification.show({
      type: 'error',
      message: `Create failed: ${error.message}`,
      duration: 0, // Persistent error
      closeable: true,
      errorColor: '#F44336'
    });
  }
}

// UPDATE with theme color consistency
async function updateProduct(id, updates) {
  try {
    await api.put(`/products/${id}`, updates);
    
    Notification.show({
      type: 'success',
      message: 'Product updated successfully',
      duration: 2000,
      themeColor: '#2196F3' // Blue theme for all operations
    });
    
  } catch (error) {
    Notification.show({
      type: 'error', 
      message: 'Update failed - please try again',
      duration: 3000,
      themeColor: '#2196F3' // Consistent blue theme
    });
  }
}

// DELETE with warning confirmation flow
async function deleteProduct(id, productName) {
  // First show warning confirmation
  Notification.show({
    type: 'warning',
    message: `Delete "${productName}"? <button onclick="confirmDelete(${id})">Confirm</button>`,
    duration: 0, // Stays until user acts
    closeable: true,
    warningColor: '#FF9800'
  });
}

async function confirmDelete(productId) {
  try {
    await api.delete(`/products/${productId}`);
    
    Notification.show({
      type: 'success',
      message: 'Product deleted permanently',
      duration: 2000,
      successColor: '#009688'
    });
    
    // Refresh list
    const products = await api.get('/products');
    renderProductList(products);
    
  } catch (error) {
    Notification.show({
      type: 'error',
      message: 'Deletion failed - product may be in use',
      duration: 4000,
      errorColor: '#E91E63'
    });
  }
}

// BATCH OPERATIONS with progress notifications
async function bulkUpdateProducts(updates) {
  let successCount = 0;
  let errorCount = 0;
  
  for (const update of updates) {
    try {
      await api.put(`/products/${update.id}`, update.data);
      successCount++;
    } catch (error) {
      errorCount++;
    }
  }
  
  // Show summary notification
  Notification.show({
    type: successCount > errorCount ? 'success' : 'warning',
    message: `Batch update: ${successCount} succeeded, ${errorCount} failed`,
    duration: 5000,
    successColor: '#4CAF50',
    warningColor: '#FF9800'
  });
}

// REAL-WORLD EXAMPLE: Product inventory management
class ProductManager {
  constructor() {
    this.api = new smQL('https://api.example.com');
  }
  
  async addProduct(productData) {
    try {
      const response = await this.api.post('/products', productData);
      
      Notification.show({
        type: 'success',
        message: `"${productData.name}" added to inventory`,
        duration: 3000,
        successColor: '#4CAF50'
      });
      
      return response;
    } catch (error) {
      Notification.show({
        type: 'error',
        message: `Inventory error: ${error.message}`,
        duration: 0, // Critical errors stay until dismissed
        closeable: true,
        errorColor: '#F44336'
      });
      throw error;
    }
  }
  
  async updateStock(productId, newStock) {
    try {
      await this.api.patch(`/products/${productId}`, { stock: newStock });
      
      Notification.show({
        type: 'success',
        message: `Stock updated to ${newStock} units`,
        duration: 2000,
        themeColor: '#2196F3' // Consistent blue for stock operations
      });
      
    } catch (error) {
      Notification.show({
        type: 'error',
        message: 'Stock update failed',
        duration: 3000,
        themeColor: '#2196F3'
      });
    }
  }
}

// Initialize product manager
const productManager = new ProductManager();

File Upload Example

            new Form('product-upload-form', 'submit', {
                onCaptured: async ({ formData, hasFiles, data }) => {
                    const urlEndpoint = `/product/upload/${data.productId}`;
                    document.getElementById('submit').disabled = true;

                    try {
                        if (!hasFiles) throw new Error("Please select a file.");

                        const response = await api.post(urlEndpoint, formData);

                        if (response._ok) {
                            alert(`Upload successful! Processed ${response.count} records.`);
                            document.getElementById('product-upload-form').reset();
                        } else {
                            throw new Error(response.error || 'Server error during upload.');
                        }
                    } catch (error) {
                        console.error('Upload Failed:', error);
                        alert(`Upload Failed: ${error.message}`);
                    } finally {
                        document.getElementById('submit').disabled = false;
                    }
                }
            });

You can visit the comprehensive smQL File Uploads docs here: smQL File Uploads.

Why Use smQL?

  • Complete solution: Combines HTTP client, form handling, and notifications
  • Lightweight: Minimal overhead
  • Flexible: Works with any backend API
  • Consistent: Standardized response format
  • Debug-friendly: Built-in logging system

License

MIT License - Free for personal and commercial use.