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
43 changes: 41 additions & 2 deletions packages/core/src/adapters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const adapter = new ObjectStackAdapter({
// Manually connect (optional, auto-connects on first request)
await adapter.connect();

// Query with filters
// Query with filters (MongoDB-like operators)
const result = await adapter.find('tasks', {
$filter: {
status: 'active',
Expand All @@ -68,14 +68,53 @@ const client = adapter.getClient();
const metadata = await client.meta.getObject('task');
```

### Filter Conversion

The adapter automatically converts MongoDB-like filter operators to **ObjectStack FilterNode AST format**. This ensures compatibility with the latest ObjectStack Protocol (v0.1.2+).

#### Supported Filter Operators

| MongoDB Operator | ObjectStack Operator | Example |
|------------------|---------------------|---------|
| `$eq` or simple value | `=` | `{ status: 'active' }` → `['status', '=', 'active']` |
| `$ne` | `!=` | `{ status: { $ne: 'archived' } }` → `['status', '!=', 'archived']` |
| `$gt` | `>` | `{ age: { $gt: 18 } }` → `['age', '>', 18]` |
| `$gte` | `>=` | `{ age: { $gte: 18 } }` → `['age', '>=', 18]` |
| `$lt` | `<` | `{ age: { $lt: 65 } }` → `['age', '<', 65]` |
| `$lte` | `<=` | `{ age: { $lte: 65 } }` → `['age', '<=', 65]` |
| `$in` | `in` | `{ status: { $in: ['active', 'pending'] } }` → `['status', 'in', ['active', 'pending']]` |
| `$nin` / `$notin` | `notin` | `{ status: { $nin: ['archived'] } }` → `['status', 'notin', ['archived']]` |
| `$contains` / `$regex` | `contains` | `{ name: { $contains: 'John' } }` → `['name', 'contains', 'John']` |
| `$startswith` | `startswith` | `{ email: { $startswith: 'admin' } }` → `['email', 'startswith', 'admin']` |
| `$between` | `between` | `{ age: { $between: [18, 65] } }` → `['age', 'between', [18, 65]]` |

#### Complex Filter Examples

**Multiple conditions** are combined with `'and'`:

```typescript
// Input
$filter: {
age: { $gte: 18, $lte: 65 },
status: 'active'
}

// Converted to AST
['and',
['age', '>=', 18],
['age', '<=', 65],
['status', '=', 'active']
]
```

### Query Parameter Mapping

The adapter automatically converts ObjectUI query parameters (OData-style) to ObjectStack protocol:

| ObjectUI ($) | ObjectStack | Description |
|--------------|-------------|-------------|
| `$select` | `select` | Field selection |
| `$filter` | `filters` | Filter conditions |
| `$filter` | `filters` (AST) | Filter conditions (converted to FilterNode AST) |
| `$orderby` | `sort` | Sort order |
| `$skip` | `skip` | Pagination offset |
| `$top` | `top` | Limit records |
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/adapters/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* 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.
*/
export { ObjectStackAdapter, createObjectStackAdapter } from './objectstack-adapter';
8 changes: 8 additions & 0 deletions packages/core/src/adapters/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* 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.
*/
export { ObjectStackAdapter, createObjectStackAdapter } from './objectstack-adapter';
95 changes: 95 additions & 0 deletions packages/core/src/adapters/objectstack-adapter.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* 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 { ObjectStackClient } from '@objectstack/client';
import type { DataSource, QueryParams, QueryResult } from '@object-ui/types';
/**
* ObjectStack Data Source Adapter
*
* Bridges the ObjectStack Client SDK with the ObjectUI DataSource interface.
* This allows Object UI applications to seamlessly integrate with ObjectStack
* backends while maintaining the universal DataSource abstraction.
*
* @example
* ```typescript
* import { ObjectStackAdapter } from '@object-ui/core/adapters';
*
* const dataSource = new ObjectStackAdapter({
* baseUrl: 'https://api.example.com',
* token: 'your-api-token'
* });
*
* const users = await dataSource.find('users', {
* $filter: { status: 'active' },
* $top: 10
* });
* ```
*/
export declare class ObjectStackAdapter<T = any> implements DataSource<T> {
private client;
private connected;
constructor(config: {
baseUrl: string;
token?: string;
fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
});
/**
* Ensure the client is connected to the server.
* Call this before making requests or it will auto-connect on first request.
*/
connect(): Promise<void>;
/**
* Find multiple records with query parameters.
* Converts OData-style params to ObjectStack query options.
*/
find(resource: string, params?: QueryParams): Promise<QueryResult<T>>;
/**
* Find a single record by ID.
*/
findOne(resource: string, id: string | number, _params?: QueryParams): Promise<T | null>;
/**
* Create a new record.
*/
create(resource: string, data: Partial<T>): Promise<T>;
/**
* Update an existing record.
*/
update(resource: string, id: string | number, data: Partial<T>): Promise<T>;
/**
* Delete a record.
*/
delete(resource: string, id: string | number): Promise<boolean>;
/**
* Bulk operations (optional implementation).
*/
bulk(resource: string, operation: 'create' | 'update' | 'delete', data: Partial<T>[]): Promise<T[]>;
/**
* Convert ObjectUI QueryParams to ObjectStack QueryOptions.
* Maps OData-style conventions to ObjectStack conventions.
*/
private convertQueryParams;
/**
* Get access to the underlying ObjectStack client for advanced operations.
*/
getClient(): ObjectStackClient;
}
/**
* Factory function to create an ObjectStack data source.
*
* @example
* ```typescript
* const dataSource = createObjectStackAdapter({
* baseUrl: process.env.API_URL,
* token: process.env.API_TOKEN
* });
* ```
*/
export declare function createObjectStackAdapter<T = any>(config: {
baseUrl: string;
token?: string;
fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
}): DataSource<T>;
188 changes: 188 additions & 0 deletions packages/core/src/adapters/objectstack-adapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/**
* 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 { ObjectStackClient } from '@objectstack/client';
import { convertFiltersToAST } from '../utils/filter-converter';
/**
* ObjectStack Data Source Adapter
*
* Bridges the ObjectStack Client SDK with the ObjectUI DataSource interface.
* This allows Object UI applications to seamlessly integrate with ObjectStack
* backends while maintaining the universal DataSource abstraction.
*
* @example
* ```typescript
* import { ObjectStackAdapter } from '@object-ui/core/adapters';
*
* const dataSource = new ObjectStackAdapter({
* baseUrl: 'https://api.example.com',
* token: 'your-api-token'
* });
*
* const users = await dataSource.find('users', {
* $filter: { status: 'active' },
* $top: 10
* });
* ```
*/
export class ObjectStackAdapter {
constructor(config) {
Object.defineProperty(this, "client", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "connected", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
this.client = new ObjectStackClient(config);
}
/**
* Ensure the client is connected to the server.
* Call this before making requests or it will auto-connect on first request.
*/
async connect() {
if (!this.connected) {
await this.client.connect();
this.connected = true;
}
}
/**
* Find multiple records with query parameters.
* Converts OData-style params to ObjectStack query options.
*/
async find(resource, params) {
await this.connect();
const queryOptions = this.convertQueryParams(params);
const result = await this.client.data.find(resource, queryOptions);
return {
data: result.value,
total: result.count,
page: params?.$skip ? Math.floor(params.$skip / (params.$top || 20)) + 1 : 1,
pageSize: params?.$top,
hasMore: result.value.length === params?.$top,
};
}
/**
* Find a single record by ID.
*/
async findOne(resource, id, _params) {
await this.connect();
try {
const record = await this.client.data.get(resource, String(id));
return record;
}
catch (error) {
// If record not found, return null instead of throwing
if (error?.status === 404) {
return null;
}
throw error;
}
}
/**
* Create a new record.
*/
async create(resource, data) {
await this.connect();
return this.client.data.create(resource, data);
}
/**
* Update an existing record.
*/
async update(resource, id, data) {
await this.connect();
return this.client.data.update(resource, String(id), data);
}
/**
* Delete a record.
*/
async delete(resource, id) {
await this.connect();
const result = await this.client.data.delete(resource, String(id));
return result.success;
}
/**
* Bulk operations (optional implementation).
*/
async bulk(resource, operation, data) {
await this.connect();
switch (operation) {
case 'create':
return this.client.data.createMany(resource, data);
case 'delete': {
const ids = data.map(item => item.id).filter(Boolean);
await this.client.data.deleteMany(resource, ids);
return [];
}
case 'update': {
// For update, we need to handle each record individually
// or use the batch update if all records get the same changes
const results = await Promise.all(data.map(item => this.client.data.update(resource, String(item.id), item)));
return results;
}
default:
throw new Error(`Unsupported bulk operation: ${operation}`);
}
}
/**
* Convert ObjectUI QueryParams to ObjectStack QueryOptions.
* Maps OData-style conventions to ObjectStack conventions.
*/
convertQueryParams(params) {
if (!params)
return {};
const options = {};
// Selection
if (params.$select) {
options.select = params.$select;
}
// Filtering - convert to ObjectStack FilterNode AST format
if (params.$filter) {
options.filters = convertFiltersToAST(params.$filter);
}
// Sorting - convert to ObjectStack format
if (params.$orderby) {
const sortArray = Object.entries(params.$orderby).map(([field, order]) => {
return order === 'desc' ? `-${field}` : field;
});
options.sort = sortArray;
}
// Pagination
if (params.$skip !== undefined) {
options.skip = params.$skip;
}
if (params.$top !== undefined) {
options.top = params.$top;
}
return options;
}
/**
* Get access to the underlying ObjectStack client for advanced operations.
*/
getClient() {
return this.client;
}
}
/**
* Factory function to create an ObjectStack data source.
*
* @example
* ```typescript
* const dataSource = createObjectStackAdapter({
* baseUrl: process.env.API_URL,
* token: process.env.API_TOKEN
* });
* ```
*/
export function createObjectStackAdapter(config) {
return new ObjectStackAdapter(config);
}
5 changes: 3 additions & 2 deletions packages/core/src/adapters/objectstack-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { ObjectStackClient, type QueryOptions as ObjectStackQueryOptions } from '@objectstack/client';
import type { DataSource, QueryParams, QueryResult } from '@object-ui/types';
import { convertFiltersToAST } from '../utils/filter-converter';

/**
* ObjectStack Data Source Adapter
Expand Down Expand Up @@ -159,9 +160,9 @@ export class ObjectStackAdapter<T = any> implements DataSource<T> {
options.select = params.$select;
}

// Filtering - convert object to simple map
// Filtering - convert to ObjectStack FilterNode AST format
if (params.$filter) {
options.filters = params.$filter;
options.filters = convertFiltersToAST(params.$filter);
}

// Sorting - convert to ObjectStack format
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/builder/schema-builder.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* @object-ui/core - Schema Builder
*
Expand Down
Loading
Loading