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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@object-ui/data-objectstack

v17.6.0

Published

ObjectStack Data Adapter for Object UI

Downloads

1,955

Readme

@object-ui/data-objectstack

Official ObjectStack data adapter for Object UI.

Overview

This package provides the ObjectStackAdapter class, which connects Object UI's universal DataSource interface with the @objectstack/client SDK.

This enables strictly typed, metadata-driven UI components to communicate seamlessly with ObjectStack backends (Steedos, Salesforce, etc.).

Installation

npm install @object-ui/data-objectstack

Note: @objectstack/client is a regular dependency of this package — it is installed and resolved along with it, so there is nothing to install separately.

Usage

Basic Setup

import { createObjectStackAdapter } from '@object-ui/data-objectstack';
import { SchemaRenderer } from '@object-ui/react';

// 1. Create the adapter
const dataSource = createObjectStackAdapter({
  baseUrl: 'https://api.example.com',
  token: 'your-api-token' // Optional if effectively handling auth elsewhere
});

// 2. Pass to the Renderer
function App() {
  return (
    <SchemaRenderer 
      schema={mySchema} 
      dataSource={dataSource} 
    />
  );
}

Advanced Configuration

const dataSource = createObjectStackAdapter({
  baseUrl: 'https://api.example.com',
  token: 'your-api-token',
  // Configure metadata cache
  cache: {
    maxSize: 100,      // Maximum number of cached schemas (default: 100)
    ttl: 5 * 60 * 1000 // Time to live in ms (default: 5 minutes)
  },
  // Configure auto-reconnect
  autoReconnect: true,           // Enable auto-reconnect (default: true)
  maxReconnectAttempts: 5,       // Max reconnection attempts (default: 3)
  reconnectDelay: 2000           // Initial delay between reconnects in ms (default: 1000)
});

Features

  • CRUD Operations: Implements find, findOne, create, update, delete.
  • Metadata Caching: Automatic LRU caching of schema metadata with TTL expiration.
  • Metadata Fetching: Implements getObjectSchema to power auto-generated forms and grids.
  • Query Translation: Converts Object UI's OData-like query parameters to ObjectStack's native query format.
  • Bulk Operations: Supports optimized batch create/update/delete with detailed error reporting.
  • Error Handling: Comprehensive error hierarchy with unique error codes and debugging details.
  • Connection Monitoring: Real-time connection state tracking with event listeners.
  • Auto-Reconnect: Automatic reconnection with exponential backoff on connection failures.
  • Batch Progress: Progress events for tracking bulk operation status.

Metadata Caching

The adapter includes built-in metadata caching to improve performance when fetching schemas:

// Get cache statistics
const stats = dataSource.getCacheStats();
console.log(`Cache hit rate: ${stats.hitRate * 100}%`);
console.log(`Cache size: ${stats.size}/${stats.maxSize}`);

// Manually invalidate cache entries
dataSource.invalidateCache('users'); // Invalidate specific schema
dataSource.invalidateCache();        // Invalidate all cached schemas

// Clear cache and statistics
dataSource.clearCache();

Cache Configuration

  • LRU Eviction: Automatically evicts least recently used entries when cache is full
  • TTL Expiration: Entries expire after the configured time-to-live from creation (default: 5 minutes)
    • Note: TTL is fixed from creation time, not sliding based on access
  • Memory Limits: Configurable maximum cache size (default: 100 entries)
  • Concurrent Access: Handles async operations safely. Note that concurrent requests for the same uncached key may result in multiple fetcher calls.

Connection State Monitoring

The adapter provides real-time connection state monitoring with automatic reconnection:

// Monitor connection state changes
const unsubscribe = dataSource.onConnectionStateChange((event) => {
  console.log('Connection state:', event.state);
  console.log('Timestamp:', new Date(event.timestamp));
  
  if (event.error) {
    console.error('Connection error:', event.error);
  }
});

// Check current connection state
console.log(dataSource.getConnectionState()); // 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error'

// Check if connected
if (dataSource.isConnected()) {
  console.log('Adapter is connected');
}

// Unsubscribe from events when done
unsubscribe();

Connection States

  • disconnected - Not connected to server
  • connecting - Attempting initial connection
  • connected - Successfully connected
  • reconnecting - Attempting to reconnect after failure
  • error - Connection failed (check event.error for details)

Auto-Reconnect

The adapter automatically attempts to reconnect on connection failures:

  • Exponential Backoff: Delay increases with each attempt (delay × 2^(attempts-1))
  • Configurable Attempts: Set maxReconnectAttempts (default: 3)
  • Configurable Delay: Set reconnectDelay for initial delay (default: 1000ms)
  • Automatic: Enabled by default, disable with autoReconnect: false

Batch Operation Progress

Track progress of bulk operations in real-time:

// Monitor batch operation progress
const unsubscribe = dataSource.onBatchProgress((event) => {
  console.log(`${event.operation}: ${event.percentage.toFixed(1)}%`);
  console.log(`Completed: ${event.completed}/${event.total}`);
  console.log(`Failed: ${event.failed}`);
});

// Perform bulk operation
const users = await dataSource.bulk('users', 'create', largeDataset);

// Unsubscribe when done
unsubscribe();

Progress Event Properties

  • operation - Operation type ('create' | 'update' | 'delete')
  • total - Total number of items
  • completed - Number of successfully completed items
  • failed - Number of failed items
  • percentage - Completion percentage (0-100)

Error Handling

The adapter provides a comprehensive error hierarchy for better error handling:

Error Types

import {
  ObjectStackError,        // Base error class
  MetadataNotFoundError,   // Schema/metadata not found (404)
  BulkOperationError,      // Bulk operation failures with partial results
  ConnectionError,         // Network/connection errors (503/504)
  AuthenticationError,     // Authentication failures (401/403)
  DataApiValidationError,  // Data validation errors (400). Its runtime `name` is
                           // still 'ValidationError' — that string is the wire
                           // discriminator shared with @objectstack/client. The
                           // SYMBOL is prefixed because @objectstack/spec/kernel
                           // owns `ValidationError` for a { field, message, code? }
                           // record (objectui#3160).
} from '@object-ui/data-objectstack';

Error Handling Example

try {
  const schema = await dataSource.getObjectSchema('users');
} catch (error) {
  if (error instanceof MetadataNotFoundError) {
    console.error(`Schema not found: ${error.details.objectName}`);
  } else if (error instanceof ConnectionError) {
    console.error(`Connection failed to: ${error.url}`);
  } else if (error instanceof AuthenticationError) {
    console.error('Authentication required');
  }
  
  // All errors have consistent structure
  console.error({
    code: error.code,
    message: error.message,
    statusCode: error.statusCode,
    details: error.details
  });
}

Bulk Operation Errors

Bulk operations provide detailed error reporting with partial success information:

try {
  await dataSource.bulk('users', 'update', records);
} catch (error) {
  if (error instanceof BulkOperationError) {
    const summary = error.getSummary();
    console.log(`${summary.successful} succeeded, ${summary.failed} failed`);
    console.log(`Failure rate: ${summary.failureRate * 100}%`);
    
    // Inspect individual failures
    summary.errors.forEach(({ index, error }) => {
      console.error(`Record ${index} failed:`, error);
    });
  }
}

Error Codes

All errors include unique error codes for programmatic handling:

  • METADATA_NOT_FOUND - Schema/metadata not found
  • BULK_OPERATION_ERROR - Bulk operation failure
  • CONNECTION_ERROR - Connection/network error
  • AUTHENTICATION_ERROR - Authentication failure
  • VALIDATION_ERROR - Data validation error
  • UNSUPPORTED_OPERATION - Unsupported operation
  • NOT_FOUND - Resource not found
  • UNKNOWN_ERROR - Unknown error

Batch Operations

The adapter supports optimized batch operations with automatic fallback:

// Batch create
const newUsers = await dataSource.bulk('users', 'create', [
  { name: 'Alice', email: '[email protected]' },
  { name: 'Bob', email: '[email protected]' },
]);

// Batch update (uses updateMany if available, falls back to individual updates)
const updated = await dataSource.bulk('users', 'update', [
  { id: '1', name: 'Alice Smith' },
  { id: '2', name: 'Bob Jones' },
]);

// Batch delete
await dataSource.bulk('users', 'delete', [
  { id: '1' },
  { id: '2' },
]);

Performance Optimizations

  • Automatically uses createMany, updateMany, deleteMany when available
  • Falls back to individual operations with detailed error tracking
  • Provides partial success reporting for resilient error handling
  • Atomic operations where supported by the backend

Cross-Object Atomic Batch (batchTransaction)

bulk() above operates on one object. To persist a set of cross-object writes as a single all-or-nothing unit — the master-detail case, where a parent and its children must commit or roll back together — use batchTransaction:

// Create a parent and a child that references it, atomically.
// `{ $ref: 0 }` resolves to the id minted by operation 0 (the parent).
await dataSource.batchTransaction([
  { object: 'invoice',      action: 'create', data: { no: 'INV-1' } },
  { object: 'invoice_line', action: 'create', data: { invoice: { $ref: 0 }, amount: 10 } },
]);

On a supporting backend this is one POST /api/v1/batch that commits or rolls back the whole set in a single server transaction — no orphaned parent if a child write fails.

Declarative capability negotiation (transactionalBatch)

Whether the adapter can rely on server atomicity is decided at connect time, not by firing a batch and reading the failure. connect() reads the capabilities.transactionalBatch flag from GET /api/v1/discovery (framework #3298), which the server sets to true only when the /batch route is mounted and the runtime engine can honour a transaction (declared === enforced):

| Discovery capabilities.transactionalBatch | batchTransaction behaviour | | --- | --- | | true | Trusts server atomicity. Calls /batch; any failure — including 404/405/501 — surfaces as a real error. No non-atomic client-side fallback. | | false | Backend can't do an atomic batch (route absent, or a runtime without transactions) → falls back to the non-atomic client-side emulation below. | | absent | Backend predates #3298 and advertises nothing → the legacy runtime probe stays: try /batch, and on 404/405/501 fall back to emulation. |

The hierarchical wire shape ({ transactionalBatch: { enabled: true } }) and the flat form the client SDK normalizes to ({ transactionalBatch: true }) are both accepted.

Non-atomic fallback

When the capability is false or absent, the adapter degrades to a client-side emulation (@object-ui/core's emulateBatchTransaction): the operations run in order and, on failure, it best-effort deletes the records it created (children before parent) before rethrowing. This is not a transaction — a create's side effects (hooks, rollups, webhooks) are not undone by a later delete, and a mid-batch network drop leaves no chance to compensate. It exists only so a save is still possible against a backend that lacks server atomicity; removing it would turn "saves, less safe" into "no save path" on older backends (objectui #2679).

Minimum supported backend

Atomic cross-object saves are guaranteed only against ObjectStack backends on the 16.x line that advertise capabilities.transactionalBatch: true — the endpoint landed in framework #1604 and its discovery capability in framework #3298. ObjectUI does not hard-require it: against an older backend a master-detail save still succeeds, but non-atomically via the fallback above. Treat the advertised capability as the floor for the atomicity guarantee, not as a connection prerequisite.

User-Scoped State Adapter

In addition to the main DataSource adapter, this package ships createObjectStackUserStateAdapter — a small factory that lets Object UI persist per-user UI state (favorites, recent items) into ObjectStack.

import { createObjectStackUserStateAdapter } from '@object-ui/data-objectstack';
import { useAttachUserStateAdapters } from '@object-ui/app-shell';

const favoritesAdapter = createObjectStackUserStateAdapter({
  dataSource,             // the ObjectStack DataSource
  userId: user.id,
  key: 'ui.favorites',    // or 'ui.recent', 'ui.grid.account.state', ...
  // resource: 'sys_user_preference',  // default — the unified per-user KV store
  // onError: (op, err) => console.warn(`[user-state] ${op} failed`, err),
});

attach('favorites', favoritesAdapter);

Backend contract

The adapter writes to the platform's unified per-user KV store — sys_user_preference — shipped by every @objectstack/plugin-auth environment. One row per (user_id, key) pair:

object: sys_user_preference
fields:
  - { name: user_id,    type: lookup(sys_user), indexed: true }
  - { name: key,        type: string,           indexed: true }
  - { name: value,      type: json }
  - { name: updated_at, type: datetime }
unique: [user_id, key]

By convention, namespace UI-trace keys under ui.* (e.g. ui.favorites, ui.recent, ui.grid.<object>.state, ui.sidebar.collapsed) so they stay easy to tell apart from user-facing preferences (theme, locale).

If the backend doesn't yet expose sys_user_preference, every call 404s and the UI silently degrades to localStorage-only persistence. See User-Scoped State Persistence for the full design.

API Reference

ObjectStackAdapter

Constructor

new ObjectStackAdapter(config: {
  baseUrl: string;
  token?: string;
  fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
  cache?: {
    maxSize?: number;
    ttl?: number;
  };
  autoReconnect?: boolean;
  maxReconnectAttempts?: number;
  reconnectDelay?: number;
})

Methods

  • connect() - Establish connection to ObjectStack server
  • find(resource, params?) - Query multiple records
  • findOne(resource, id, params?) - Get a single record by ID
  • create(resource, data) - Create a new record
  • update(resource, id, data) - Update an existing record
  • delete(resource, id) - Delete a record
  • bulk(resource, operation, data) - Batch operations (create/update/delete)
  • batchTransaction(operations) - Cross-object atomic batch (master-detail); atomic when the backend advertises transactionalBatch, else non-atomic client-side fallback
  • getObjectSchema(objectName) - Get schema metadata (cached)
  • getCacheStats() - Get cache statistics
  • invalidateCache(key?) - Invalidate cache entries
  • clearCache() - Clear all cache entries
  • getClient() - Access underlying ObjectStack client
  • getConnectionState() - Get current connection state
  • isConnected() - Check if adapter is connected
  • onConnectionStateChange(listener) - Subscribe to connection state changes (returns unsubscribe function)
  • onBatchProgress(listener) - Subscribe to batch operation progress (returns unsubscribe function)

Best Practices

  1. Enable Caching: Use default cache settings for optimal performance
  2. Handle Errors: Use typed error handling for better user experience
  3. Batch Operations: Use bulk methods for large datasets
  4. Monitor Cache: Check cache hit rates in production
  5. Invalidate Wisely: Clear cache after schema changes
  6. Connection Monitoring: Subscribe to connection state changes for better UX
  7. Auto-Reconnect: Use default auto-reconnect settings for resilient applications
  8. Batch Progress: Monitor progress for long-running bulk operations

Troubleshooting

Common Issues

Schema Not Found

// Error: MetadataNotFoundError
// Solution: Verify object name and ensure schema exists on server
const schema = await dataSource.getObjectSchema('correct_object_name');

Connection Errors

// Error: ConnectionError
// Solution: Check baseUrl and network connectivity
const dataSource = createObjectStackAdapter({
  baseUrl: 'https://correct-url.example.com',
  token: 'valid-token'
});

Cache Issues

// Clear cache if stale data is being returned
dataSource.clearCache();

// Or invalidate specific entries
dataSource.invalidateCache('users');

Links

License

MIT — see LICENSE.