Skip to content

Commit ceea0d0

Browse files
Copilothotlong
andcommitted
refactor: Extract filter conversion to shared utility and improve error handling
Address code review feedback from @hotlong: 1. Extract filter conversion logic to shared utility (filter-converter.ts) 2. Add warning for $regex operator (limited regex support) 3. Throw error for unknown operators (no silent failures) 4. Add FilterNode type definition for better type safety 5. Add comprehensive tests for filter conversion Changes: - Created packages/core/src/utils/filter-converter.ts with convertFiltersToAST() and convertOperatorToAST() - Updated objectstack-adapter.ts to use shared utility - Updated ObjectQLDataSource.ts to use shared utility - Added 12 new tests covering all filter conversion scenarios - All 119 tests passing Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 9719099 commit ceea0d0

22 files changed

+851
-194
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* ObjectUI
3+
* Copyright (c) 2024-present ObjectStack Inc.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
export { ObjectStackAdapter, createObjectStackAdapter } from './objectstack-adapter';
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* ObjectUI
3+
* Copyright (c) 2024-present ObjectStack Inc.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
export { ObjectStackAdapter, createObjectStackAdapter } from './objectstack-adapter';
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* ObjectUI
3+
* Copyright (c) 2024-present ObjectStack Inc.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
import { ObjectStackClient } from '@objectstack/client';
9+
import type { DataSource, QueryParams, QueryResult } from '@object-ui/types';
10+
/**
11+
* ObjectStack Data Source Adapter
12+
*
13+
* Bridges the ObjectStack Client SDK with the ObjectUI DataSource interface.
14+
* This allows Object UI applications to seamlessly integrate with ObjectStack
15+
* backends while maintaining the universal DataSource abstraction.
16+
*
17+
* @example
18+
* ```typescript
19+
* import { ObjectStackAdapter } from '@object-ui/core/adapters';
20+
*
21+
* const dataSource = new ObjectStackAdapter({
22+
* baseUrl: 'https://api.example.com',
23+
* token: 'your-api-token'
24+
* });
25+
*
26+
* const users = await dataSource.find('users', {
27+
* $filter: { status: 'active' },
28+
* $top: 10
29+
* });
30+
* ```
31+
*/
32+
export declare class ObjectStackAdapter<T = any> implements DataSource<T> {
33+
private client;
34+
private connected;
35+
constructor(config: {
36+
baseUrl: string;
37+
token?: string;
38+
fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
39+
});
40+
/**
41+
* Ensure the client is connected to the server.
42+
* Call this before making requests or it will auto-connect on first request.
43+
*/
44+
connect(): Promise<void>;
45+
/**
46+
* Find multiple records with query parameters.
47+
* Converts OData-style params to ObjectStack query options.
48+
*/
49+
find(resource: string, params?: QueryParams): Promise<QueryResult<T>>;
50+
/**
51+
* Find a single record by ID.
52+
*/
53+
findOne(resource: string, id: string | number, _params?: QueryParams): Promise<T | null>;
54+
/**
55+
* Create a new record.
56+
*/
57+
create(resource: string, data: Partial<T>): Promise<T>;
58+
/**
59+
* Update an existing record.
60+
*/
61+
update(resource: string, id: string | number, data: Partial<T>): Promise<T>;
62+
/**
63+
* Delete a record.
64+
*/
65+
delete(resource: string, id: string | number): Promise<boolean>;
66+
/**
67+
* Bulk operations (optional implementation).
68+
*/
69+
bulk(resource: string, operation: 'create' | 'update' | 'delete', data: Partial<T>[]): Promise<T[]>;
70+
/**
71+
* Convert ObjectUI QueryParams to ObjectStack QueryOptions.
72+
* Maps OData-style conventions to ObjectStack conventions.
73+
*/
74+
private convertQueryParams;
75+
/**
76+
* Get access to the underlying ObjectStack client for advanced operations.
77+
*/
78+
getClient(): ObjectStackClient;
79+
}
80+
/**
81+
* Factory function to create an ObjectStack data source.
82+
*
83+
* @example
84+
* ```typescript
85+
* const dataSource = createObjectStackAdapter({
86+
* baseUrl: process.env.API_URL,
87+
* token: process.env.API_TOKEN
88+
* });
89+
* ```
90+
*/
91+
export declare function createObjectStackAdapter<T = any>(config: {
92+
baseUrl: string;
93+
token?: string;
94+
fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
95+
}): DataSource<T>;
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/**
2+
* ObjectUI
3+
* Copyright (c) 2024-present ObjectStack Inc.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
import { ObjectStackClient } from '@objectstack/client';
9+
import { convertFiltersToAST } from '../utils/filter-converter';
10+
/**
11+
* ObjectStack Data Source Adapter
12+
*
13+
* Bridges the ObjectStack Client SDK with the ObjectUI DataSource interface.
14+
* This allows Object UI applications to seamlessly integrate with ObjectStack
15+
* backends while maintaining the universal DataSource abstraction.
16+
*
17+
* @example
18+
* ```typescript
19+
* import { ObjectStackAdapter } from '@object-ui/core/adapters';
20+
*
21+
* const dataSource = new ObjectStackAdapter({
22+
* baseUrl: 'https://api.example.com',
23+
* token: 'your-api-token'
24+
* });
25+
*
26+
* const users = await dataSource.find('users', {
27+
* $filter: { status: 'active' },
28+
* $top: 10
29+
* });
30+
* ```
31+
*/
32+
export class ObjectStackAdapter {
33+
constructor(config) {
34+
Object.defineProperty(this, "client", {
35+
enumerable: true,
36+
configurable: true,
37+
writable: true,
38+
value: void 0
39+
});
40+
Object.defineProperty(this, "connected", {
41+
enumerable: true,
42+
configurable: true,
43+
writable: true,
44+
value: false
45+
});
46+
this.client = new ObjectStackClient(config);
47+
}
48+
/**
49+
* Ensure the client is connected to the server.
50+
* Call this before making requests or it will auto-connect on first request.
51+
*/
52+
async connect() {
53+
if (!this.connected) {
54+
await this.client.connect();
55+
this.connected = true;
56+
}
57+
}
58+
/**
59+
* Find multiple records with query parameters.
60+
* Converts OData-style params to ObjectStack query options.
61+
*/
62+
async find(resource, params) {
63+
await this.connect();
64+
const queryOptions = this.convertQueryParams(params);
65+
const result = await this.client.data.find(resource, queryOptions);
66+
return {
67+
data: result.value,
68+
total: result.count,
69+
page: params?.$skip ? Math.floor(params.$skip / (params.$top || 20)) + 1 : 1,
70+
pageSize: params?.$top,
71+
hasMore: result.value.length === params?.$top,
72+
};
73+
}
74+
/**
75+
* Find a single record by ID.
76+
*/
77+
async findOne(resource, id, _params) {
78+
await this.connect();
79+
try {
80+
const record = await this.client.data.get(resource, String(id));
81+
return record;
82+
}
83+
catch (error) {
84+
// If record not found, return null instead of throwing
85+
if (error?.status === 404) {
86+
return null;
87+
}
88+
throw error;
89+
}
90+
}
91+
/**
92+
* Create a new record.
93+
*/
94+
async create(resource, data) {
95+
await this.connect();
96+
return this.client.data.create(resource, data);
97+
}
98+
/**
99+
* Update an existing record.
100+
*/
101+
async update(resource, id, data) {
102+
await this.connect();
103+
return this.client.data.update(resource, String(id), data);
104+
}
105+
/**
106+
* Delete a record.
107+
*/
108+
async delete(resource, id) {
109+
await this.connect();
110+
const result = await this.client.data.delete(resource, String(id));
111+
return result.success;
112+
}
113+
/**
114+
* Bulk operations (optional implementation).
115+
*/
116+
async bulk(resource, operation, data) {
117+
await this.connect();
118+
switch (operation) {
119+
case 'create':
120+
return this.client.data.createMany(resource, data);
121+
case 'delete': {
122+
const ids = data.map(item => item.id).filter(Boolean);
123+
await this.client.data.deleteMany(resource, ids);
124+
return [];
125+
}
126+
case 'update': {
127+
// For update, we need to handle each record individually
128+
// or use the batch update if all records get the same changes
129+
const results = await Promise.all(data.map(item => this.client.data.update(resource, String(item.id), item)));
130+
return results;
131+
}
132+
default:
133+
throw new Error(`Unsupported bulk operation: ${operation}`);
134+
}
135+
}
136+
/**
137+
* Convert ObjectUI QueryParams to ObjectStack QueryOptions.
138+
* Maps OData-style conventions to ObjectStack conventions.
139+
*/
140+
convertQueryParams(params) {
141+
if (!params)
142+
return {};
143+
const options = {};
144+
// Selection
145+
if (params.$select) {
146+
options.select = params.$select;
147+
}
148+
// Filtering - convert to ObjectStack FilterNode AST format
149+
if (params.$filter) {
150+
options.filters = convertFiltersToAST(params.$filter);
151+
}
152+
// Sorting - convert to ObjectStack format
153+
if (params.$orderby) {
154+
const sortArray = Object.entries(params.$orderby).map(([field, order]) => {
155+
return order === 'desc' ? `-${field}` : field;
156+
});
157+
options.sort = sortArray;
158+
}
159+
// Pagination
160+
if (params.$skip !== undefined) {
161+
options.skip = params.$skip;
162+
}
163+
if (params.$top !== undefined) {
164+
options.top = params.$top;
165+
}
166+
return options;
167+
}
168+
/**
169+
* Get access to the underlying ObjectStack client for advanced operations.
170+
*/
171+
getClient() {
172+
return this.client;
173+
}
174+
}
175+
/**
176+
* Factory function to create an ObjectStack data source.
177+
*
178+
* @example
179+
* ```typescript
180+
* const dataSource = createObjectStackAdapter({
181+
* baseUrl: process.env.API_URL,
182+
* token: process.env.API_TOKEN
183+
* });
184+
* ```
185+
*/
186+
export function createObjectStackAdapter(config) {
187+
return new ObjectStackAdapter(config);
188+
}

0 commit comments

Comments
 (0)