Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/console/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ The standard runtime UI for ObjectStack applications. This package provides the

## Features

- **Spec-Compliant**: Fully implements ObjectStack Spec v0.8.2
- **Spec-Compliant**: Fully implements ObjectStack Spec v0.9.0
- **Dynamic UI**: Renders Dashboards, Grids, and Forms based on JSON schemas
- **Multi-App Support**: Switch between different apps defined in your stack
- **Plugin Architecture**: Can be loaded as a static plugin in the ObjectStack Runtime
Expand All @@ -27,8 +27,8 @@ This console implements the following ObjectStack Spec features:

### Navigation Support
- ✅ `object` - Navigate to object list views
- ✅ `dashboard` - Navigate to dashboards (planned)
- ✅ `page` - Navigate to custom pages (planned)
- ✅ `dashboard` - Navigate to dashboards
- ✅ `page` - Navigate to custom pages
- ✅ `url` - External URL navigation with target support
- ✅ `group` - Nested navigation groups
- ✅ Navigation item visibility conditions
Expand Down
4 changes: 2 additions & 2 deletions apps/console/src/__tests__/SpecCompliance.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import appConfig from '../../objectstack.config';
/**
* Spec Compliance Tests
*
* These tests verify that the console properly implements the ObjectStack Spec v0.8.2
* These tests verify that the console properly implements the ObjectStack Spec v0.9.0
* See: apps/console/SPEC_ALIGNMENT.md for full compliance details
*/

describe('ObjectStack Spec v0.8.2 Compliance', () => {
describe('ObjectStack Spec v0.9.0 Compliance', () => {

describe('AppSchema Validation', () => {
it('should have at least one app defined', () => {
Expand Down
90 changes: 89 additions & 1 deletion packages/data-objectstack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ const dataSource = createObjectStackAdapter({
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)
});
```

Expand All @@ -61,6 +65,9 @@ const dataSource = createObjectStackAdapter({
- ✅ **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

Expand Down Expand Up @@ -88,6 +95,77 @@ dataSource.clearCache();
- **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:

```typescript
// 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:

```typescript
// 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:
Expand Down Expand Up @@ -209,6 +287,9 @@ new ObjectStackAdapter(config: {
maxSize?: number;
ttl?: number;
};
autoReconnect?: boolean;
maxReconnectAttempts?: number;
reconnectDelay?: number;
})
```

Expand All @@ -226,6 +307,10 @@ new ObjectStackAdapter(config: {
- `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

Expand All @@ -234,6 +319,9 @@ new ObjectStackAdapter(config: {
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

Expand Down
100 changes: 100 additions & 0 deletions packages/data-objectstack/src/connection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* ObjectUI
* Copyright (c) 2024-present ObjectStack Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { ObjectStackAdapter, ConnectionState, ConnectionStateEvent, BatchProgressEvent } from './index';

describe('Connection State Monitoring', () => {
let adapter: ObjectStackAdapter;

beforeEach(() => {
adapter = new ObjectStackAdapter({
baseUrl: 'http://localhost:3000',
autoReconnect: false, // Disable auto-reconnect for testing
});
});

it('should initialize with disconnected state', () => {
expect(adapter.getConnectionState()).toBe('disconnected');
expect(adapter.isConnected()).toBe(false);
});

it('should allow subscribing to connection state changes', () => {
const listener = vi.fn();
const unsubscribe = adapter.onConnectionStateChange(listener);

expect(typeof unsubscribe).toBe('function');
expect(listener).not.toHaveBeenCalled();

// Cleanup
unsubscribe();
});

it('should allow subscribing to batch progress events', () => {
const listener = vi.fn();
const unsubscribe = adapter.onBatchProgress(listener);

expect(typeof unsubscribe).toBe('function');
expect(listener).not.toHaveBeenCalled();

// Cleanup
unsubscribe();
});

it('should unsubscribe connection state listener', () => {
const listener = vi.fn();
const unsubscribe = adapter.onConnectionStateChange(listener);

// Unsubscribe
unsubscribe();

// Listener should not be called after unsubscribe
// (We can't easily test this without triggering a connection state change)
});

it('should unsubscribe batch progress listener', () => {
const listener = vi.fn();
const unsubscribe = adapter.onBatchProgress(listener);

// Unsubscribe
unsubscribe();

// Listener should not be called after unsubscribe
});

it('should support auto-reconnect configuration', () => {
const adapterWithReconnect = new ObjectStackAdapter({
baseUrl: 'http://localhost:3000',
autoReconnect: true,
maxReconnectAttempts: 5,
reconnectDelay: 2000,
});

expect(adapterWithReconnect.getConnectionState()).toBe('disconnected');
});
});

describe('Batch Progress Events', () => {
let adapter: ObjectStackAdapter;

beforeEach(() => {
adapter = new ObjectStackAdapter({
baseUrl: 'http://localhost:3000',
});
});

it('should allow subscribing to batch progress', () => {
const listener = vi.fn();
const unsubscribe = adapter.onBatchProgress(listener);

expect(typeof unsubscribe).toBe('function');

// Cleanup
unsubscribe();
});
});
Comment on lines +12 to +100
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage is insufficient for the new connection state and batch progress features. The tests only verify that:

  1. Listeners can be subscribed/unsubscribed
  2. Initial state is 'disconnected'
  3. Configuration is accepted

The tests do not verify:

  • That connection state actually changes through the lifecycle (disconnected → connecting → connected)
  • That listeners are actually called when state changes occur
  • That auto-reconnect attempts are made on connection failure
  • That exponential backoff delays are applied correctly
  • That batch progress events are emitted during bulk operations
  • That the reconnect counter is reset on successful connection
  • Edge cases like maximum reconnect attempts being reached

Consider adding integration tests that mock the ObjectStackClient to test the full connection lifecycle and verify that events are emitted correctly.

Copilot uses AI. Check for mistakes.
Loading
Loading