@object-ui/data-objectstack 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 ObjectQL
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # @object-ui/data-objectstack
2
+
3
+ Official ObjectStack data adapter for Object UI.
4
+
5
+ ## Overview
6
+
7
+ This package provides the `ObjectStackAdapter` class, which connects Object UI's universal `DataSource` interface with the `@objectstack/client` SDK.
8
+
9
+ This enables strictly typed, metadata-driven UI components to communicate seamlessly with ObjectStack backends (Steedos, Salesforce, etc.).
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @object-ui/data-objectstack @objectstack/client
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```typescript
20
+ import { createObjectStackAdapter } from '@object-ui/data-objectstack';
21
+ import { SchemaRenderer } from '@object-ui/react';
22
+
23
+ // 1. Create the adapter
24
+ const dataSource = createObjectStackAdapter({
25
+ baseUrl: 'https://api.example.com',
26
+ token: 'your-api-token' // Optional if effectively handling auth elsewhere
27
+ });
28
+
29
+ // 2. Pass to the Renderer
30
+ function App() {
31
+ return (
32
+ <SchemaRenderer
33
+ schema={mySchema}
34
+ dataSource={dataSource}
35
+ />
36
+ );
37
+ }
38
+ ```
39
+
40
+ ## Features
41
+
42
+ - ✅ **CRUD Operations**: Implements `find`, `findOne`, `create`, `update`, `delete`.
43
+ - ✅ **Metadata Fetching**: Implements `getObjectSchema` to power auto-generated forms and grids.
44
+ - ✅ **Query Translation**: Converts Object UI's OData-like query parameters to ObjectStack's native query format.
45
+ - ✅ **Bulk Operations**: Supports batch create/update/delete.
package/dist/index.cjs ADDED
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
21
+
22
+ // src/index.ts
23
+ var index_exports = {};
24
+ __export(index_exports, {
25
+ ObjectStackAdapter: () => ObjectStackAdapter,
26
+ createObjectStackAdapter: () => createObjectStackAdapter
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+ var import_client = require("@objectstack/client");
30
+ var import_core = require("@object-ui/core");
31
+ var ObjectStackAdapter = class {
32
+ constructor(config) {
33
+ __publicField(this, "client");
34
+ __publicField(this, "connected", false);
35
+ this.client = new import_client.ObjectStackClient(config);
36
+ }
37
+ /**
38
+ * Ensure the client is connected to the server.
39
+ * Call this before making requests or it will auto-connect on first request.
40
+ */
41
+ async connect() {
42
+ if (!this.connected) {
43
+ await this.client.connect();
44
+ this.connected = true;
45
+ }
46
+ }
47
+ /**
48
+ * Find multiple records with query parameters.
49
+ * Converts OData-style params to ObjectStack query options.
50
+ */
51
+ async find(resource, params) {
52
+ await this.connect();
53
+ const queryOptions = this.convertQueryParams(params);
54
+ const result = await this.client.data.find(resource, queryOptions);
55
+ return {
56
+ data: result.value,
57
+ total: result.count,
58
+ page: params?.$skip ? Math.floor(params.$skip / (params.$top || 20)) + 1 : 1,
59
+ pageSize: params?.$top,
60
+ hasMore: result.value.length === params?.$top
61
+ };
62
+ }
63
+ /**
64
+ * Find a single record by ID.
65
+ */
66
+ async findOne(resource, id, _params) {
67
+ await this.connect();
68
+ try {
69
+ const record = await this.client.data.get(resource, String(id));
70
+ return record;
71
+ } catch (error) {
72
+ if (error?.status === 404) {
73
+ return null;
74
+ }
75
+ throw error;
76
+ }
77
+ }
78
+ /**
79
+ * Create a new record.
80
+ */
81
+ async create(resource, data) {
82
+ await this.connect();
83
+ return this.client.data.create(resource, data);
84
+ }
85
+ /**
86
+ * Update an existing record.
87
+ */
88
+ async update(resource, id, data) {
89
+ await this.connect();
90
+ return this.client.data.update(resource, String(id), data);
91
+ }
92
+ /**
93
+ * Delete a record.
94
+ */
95
+ async delete(resource, id) {
96
+ await this.connect();
97
+ const result = await this.client.data.delete(resource, String(id));
98
+ return result.success;
99
+ }
100
+ /**
101
+ * Bulk operations (optional implementation).
102
+ */
103
+ async bulk(resource, operation, data) {
104
+ await this.connect();
105
+ switch (operation) {
106
+ case "create":
107
+ return this.client.data.createMany(resource, data);
108
+ case "delete": {
109
+ const ids = data.map((item) => item.id).filter(Boolean);
110
+ await this.client.data.deleteMany(resource, ids);
111
+ return [];
112
+ }
113
+ case "update": {
114
+ const results = await Promise.all(
115
+ data.map(
116
+ (item) => this.client.data.update(resource, String(item.id), item)
117
+ )
118
+ );
119
+ return results;
120
+ }
121
+ default:
122
+ throw new Error(`Unsupported bulk operation: ${operation}`);
123
+ }
124
+ }
125
+ /**
126
+ * Convert ObjectUI QueryParams to ObjectStack QueryOptions.
127
+ * Maps OData-style conventions to ObjectStack conventions.
128
+ */
129
+ convertQueryParams(params) {
130
+ if (!params) return {};
131
+ const options = {};
132
+ if (params.$select) {
133
+ options.select = params.$select;
134
+ }
135
+ if (params.$filter) {
136
+ options.filters = (0, import_core.convertFiltersToAST)(params.$filter);
137
+ }
138
+ if (params.$orderby) {
139
+ const sortArray = Object.entries(params.$orderby).map(([field, order]) => {
140
+ return order === "desc" ? `-${field}` : field;
141
+ });
142
+ options.sort = sortArray;
143
+ }
144
+ if (params.$skip !== void 0) {
145
+ options.skip = params.$skip;
146
+ }
147
+ if (params.$top !== void 0) {
148
+ options.top = params.$top;
149
+ }
150
+ return options;
151
+ }
152
+ /**
153
+ * Get object schema/metadata from ObjectStack.
154
+ *
155
+ * @param objectName - Object name
156
+ * @returns Promise resolving to the object schema
157
+ */
158
+ async getObjectSchema(objectName) {
159
+ await this.connect();
160
+ try {
161
+ const schema = await this.client.meta.getObject(objectName);
162
+ return schema;
163
+ } catch (error) {
164
+ console.error(`Failed to fetch schema for ${objectName}:`, error);
165
+ throw error;
166
+ }
167
+ }
168
+ /**
169
+ * Get access to the underlying ObjectStack client for advanced operations.
170
+ */
171
+ getClient() {
172
+ return this.client;
173
+ }
174
+ };
175
+ function createObjectStackAdapter(config) {
176
+ return new ObjectStackAdapter(config);
177
+ }
178
+ // Annotate the CommonJS export names for ESM import in node:
179
+ 0 && (module.exports = {
180
+ ObjectStackAdapter,
181
+ createObjectStackAdapter
182
+ });
@@ -0,0 +1,106 @@
1
+ import { ObjectStackClient } from '@objectstack/client';
2
+ import { DataSource, QueryParams, QueryResult } from '@object-ui/types';
3
+
4
+ /**
5
+ * ObjectUI
6
+ * Copyright (c) 2024-present ObjectStack Inc.
7
+ *
8
+ * This source code is licensed under the MIT license found in the
9
+ * LICENSE file in the root directory of this source tree.
10
+ */
11
+
12
+ /**
13
+ * ObjectStack Data Source Adapter
14
+ *
15
+ * Bridges the ObjectStack Client SDK with the ObjectUI DataSource interface.
16
+ * This allows Object UI applications to seamlessly integrate with ObjectStack
17
+ * backends while maintaining the universal DataSource abstraction.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * import { ObjectStackAdapter } from '@object-ui/data-objectstack';
22
+ *
23
+ * const dataSource = new ObjectStackAdapter({
24
+ * baseUrl: 'https://api.example.com',
25
+ * token: 'your-api-token'
26
+ * });
27
+ *
28
+ * const users = await dataSource.find('users', {
29
+ * $filter: { status: 'active' },
30
+ * $top: 10
31
+ * });
32
+ * ```
33
+ */
34
+ declare class ObjectStackAdapter<T = any> implements DataSource<T> {
35
+ private client;
36
+ private connected;
37
+ constructor(config: {
38
+ baseUrl: string;
39
+ token?: string;
40
+ fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
41
+ });
42
+ /**
43
+ * Ensure the client is connected to the server.
44
+ * Call this before making requests or it will auto-connect on first request.
45
+ */
46
+ connect(): Promise<void>;
47
+ /**
48
+ * Find multiple records with query parameters.
49
+ * Converts OData-style params to ObjectStack query options.
50
+ */
51
+ find(resource: string, params?: QueryParams): Promise<QueryResult<T>>;
52
+ /**
53
+ * Find a single record by ID.
54
+ */
55
+ findOne(resource: string, id: string | number, _params?: QueryParams): Promise<T | null>;
56
+ /**
57
+ * Create a new record.
58
+ */
59
+ create(resource: string, data: Partial<T>): Promise<T>;
60
+ /**
61
+ * Update an existing record.
62
+ */
63
+ update(resource: string, id: string | number, data: Partial<T>): Promise<T>;
64
+ /**
65
+ * Delete a record.
66
+ */
67
+ delete(resource: string, id: string | number): Promise<boolean>;
68
+ /**
69
+ * Bulk operations (optional implementation).
70
+ */
71
+ bulk(resource: string, operation: 'create' | 'update' | 'delete', data: Partial<T>[]): Promise<T[]>;
72
+ /**
73
+ * Convert ObjectUI QueryParams to ObjectStack QueryOptions.
74
+ * Maps OData-style conventions to ObjectStack conventions.
75
+ */
76
+ private convertQueryParams;
77
+ /**
78
+ * Get object schema/metadata from ObjectStack.
79
+ *
80
+ * @param objectName - Object name
81
+ * @returns Promise resolving to the object schema
82
+ */
83
+ getObjectSchema(objectName: string): Promise<any>;
84
+ /**
85
+ * Get access to the underlying ObjectStack client for advanced operations.
86
+ */
87
+ getClient(): ObjectStackClient;
88
+ }
89
+ /**
90
+ * Factory function to create an ObjectStack data source.
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * const dataSource = createObjectStackAdapter({
95
+ * baseUrl: process.env.API_URL,
96
+ * token: process.env.API_TOKEN
97
+ * });
98
+ * ```
99
+ */
100
+ declare function createObjectStackAdapter<T = any>(config: {
101
+ baseUrl: string;
102
+ token?: string;
103
+ fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
104
+ }): DataSource<T>;
105
+
106
+ export { ObjectStackAdapter, createObjectStackAdapter };
@@ -0,0 +1,106 @@
1
+ import { ObjectStackClient } from '@objectstack/client';
2
+ import { DataSource, QueryParams, QueryResult } from '@object-ui/types';
3
+
4
+ /**
5
+ * ObjectUI
6
+ * Copyright (c) 2024-present ObjectStack Inc.
7
+ *
8
+ * This source code is licensed under the MIT license found in the
9
+ * LICENSE file in the root directory of this source tree.
10
+ */
11
+
12
+ /**
13
+ * ObjectStack Data Source Adapter
14
+ *
15
+ * Bridges the ObjectStack Client SDK with the ObjectUI DataSource interface.
16
+ * This allows Object UI applications to seamlessly integrate with ObjectStack
17
+ * backends while maintaining the universal DataSource abstraction.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * import { ObjectStackAdapter } from '@object-ui/data-objectstack';
22
+ *
23
+ * const dataSource = new ObjectStackAdapter({
24
+ * baseUrl: 'https://api.example.com',
25
+ * token: 'your-api-token'
26
+ * });
27
+ *
28
+ * const users = await dataSource.find('users', {
29
+ * $filter: { status: 'active' },
30
+ * $top: 10
31
+ * });
32
+ * ```
33
+ */
34
+ declare class ObjectStackAdapter<T = any> implements DataSource<T> {
35
+ private client;
36
+ private connected;
37
+ constructor(config: {
38
+ baseUrl: string;
39
+ token?: string;
40
+ fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
41
+ });
42
+ /**
43
+ * Ensure the client is connected to the server.
44
+ * Call this before making requests or it will auto-connect on first request.
45
+ */
46
+ connect(): Promise<void>;
47
+ /**
48
+ * Find multiple records with query parameters.
49
+ * Converts OData-style params to ObjectStack query options.
50
+ */
51
+ find(resource: string, params?: QueryParams): Promise<QueryResult<T>>;
52
+ /**
53
+ * Find a single record by ID.
54
+ */
55
+ findOne(resource: string, id: string | number, _params?: QueryParams): Promise<T | null>;
56
+ /**
57
+ * Create a new record.
58
+ */
59
+ create(resource: string, data: Partial<T>): Promise<T>;
60
+ /**
61
+ * Update an existing record.
62
+ */
63
+ update(resource: string, id: string | number, data: Partial<T>): Promise<T>;
64
+ /**
65
+ * Delete a record.
66
+ */
67
+ delete(resource: string, id: string | number): Promise<boolean>;
68
+ /**
69
+ * Bulk operations (optional implementation).
70
+ */
71
+ bulk(resource: string, operation: 'create' | 'update' | 'delete', data: Partial<T>[]): Promise<T[]>;
72
+ /**
73
+ * Convert ObjectUI QueryParams to ObjectStack QueryOptions.
74
+ * Maps OData-style conventions to ObjectStack conventions.
75
+ */
76
+ private convertQueryParams;
77
+ /**
78
+ * Get object schema/metadata from ObjectStack.
79
+ *
80
+ * @param objectName - Object name
81
+ * @returns Promise resolving to the object schema
82
+ */
83
+ getObjectSchema(objectName: string): Promise<any>;
84
+ /**
85
+ * Get access to the underlying ObjectStack client for advanced operations.
86
+ */
87
+ getClient(): ObjectStackClient;
88
+ }
89
+ /**
90
+ * Factory function to create an ObjectStack data source.
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * const dataSource = createObjectStackAdapter({
95
+ * baseUrl: process.env.API_URL,
96
+ * token: process.env.API_TOKEN
97
+ * });
98
+ * ```
99
+ */
100
+ declare function createObjectStackAdapter<T = any>(config: {
101
+ baseUrl: string;
102
+ token?: string;
103
+ fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
104
+ }): DataSource<T>;
105
+
106
+ export { ObjectStackAdapter, createObjectStackAdapter };
package/dist/index.js ADDED
@@ -0,0 +1,158 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+ // src/index.ts
6
+ import { ObjectStackClient } from "@objectstack/client";
7
+ import { convertFiltersToAST } from "@object-ui/core";
8
+ var ObjectStackAdapter = class {
9
+ constructor(config) {
10
+ __publicField(this, "client");
11
+ __publicField(this, "connected", false);
12
+ this.client = new ObjectStackClient(config);
13
+ }
14
+ /**
15
+ * Ensure the client is connected to the server.
16
+ * Call this before making requests or it will auto-connect on first request.
17
+ */
18
+ async connect() {
19
+ if (!this.connected) {
20
+ await this.client.connect();
21
+ this.connected = true;
22
+ }
23
+ }
24
+ /**
25
+ * Find multiple records with query parameters.
26
+ * Converts OData-style params to ObjectStack query options.
27
+ */
28
+ async find(resource, params) {
29
+ await this.connect();
30
+ const queryOptions = this.convertQueryParams(params);
31
+ const result = await this.client.data.find(resource, queryOptions);
32
+ return {
33
+ data: result.value,
34
+ total: result.count,
35
+ page: params?.$skip ? Math.floor(params.$skip / (params.$top || 20)) + 1 : 1,
36
+ pageSize: params?.$top,
37
+ hasMore: result.value.length === params?.$top
38
+ };
39
+ }
40
+ /**
41
+ * Find a single record by ID.
42
+ */
43
+ async findOne(resource, id, _params) {
44
+ await this.connect();
45
+ try {
46
+ const record = await this.client.data.get(resource, String(id));
47
+ return record;
48
+ } catch (error) {
49
+ if (error?.status === 404) {
50
+ return null;
51
+ }
52
+ throw error;
53
+ }
54
+ }
55
+ /**
56
+ * Create a new record.
57
+ */
58
+ async create(resource, data) {
59
+ await this.connect();
60
+ return this.client.data.create(resource, data);
61
+ }
62
+ /**
63
+ * Update an existing record.
64
+ */
65
+ async update(resource, id, data) {
66
+ await this.connect();
67
+ return this.client.data.update(resource, String(id), data);
68
+ }
69
+ /**
70
+ * Delete a record.
71
+ */
72
+ async delete(resource, id) {
73
+ await this.connect();
74
+ const result = await this.client.data.delete(resource, String(id));
75
+ return result.success;
76
+ }
77
+ /**
78
+ * Bulk operations (optional implementation).
79
+ */
80
+ async bulk(resource, operation, data) {
81
+ await this.connect();
82
+ switch (operation) {
83
+ case "create":
84
+ return this.client.data.createMany(resource, data);
85
+ case "delete": {
86
+ const ids = data.map((item) => item.id).filter(Boolean);
87
+ await this.client.data.deleteMany(resource, ids);
88
+ return [];
89
+ }
90
+ case "update": {
91
+ const results = await Promise.all(
92
+ data.map(
93
+ (item) => this.client.data.update(resource, String(item.id), item)
94
+ )
95
+ );
96
+ return results;
97
+ }
98
+ default:
99
+ throw new Error(`Unsupported bulk operation: ${operation}`);
100
+ }
101
+ }
102
+ /**
103
+ * Convert ObjectUI QueryParams to ObjectStack QueryOptions.
104
+ * Maps OData-style conventions to ObjectStack conventions.
105
+ */
106
+ convertQueryParams(params) {
107
+ if (!params) return {};
108
+ const options = {};
109
+ if (params.$select) {
110
+ options.select = params.$select;
111
+ }
112
+ if (params.$filter) {
113
+ options.filters = convertFiltersToAST(params.$filter);
114
+ }
115
+ if (params.$orderby) {
116
+ const sortArray = Object.entries(params.$orderby).map(([field, order]) => {
117
+ return order === "desc" ? `-${field}` : field;
118
+ });
119
+ options.sort = sortArray;
120
+ }
121
+ if (params.$skip !== void 0) {
122
+ options.skip = params.$skip;
123
+ }
124
+ if (params.$top !== void 0) {
125
+ options.top = params.$top;
126
+ }
127
+ return options;
128
+ }
129
+ /**
130
+ * Get object schema/metadata from ObjectStack.
131
+ *
132
+ * @param objectName - Object name
133
+ * @returns Promise resolving to the object schema
134
+ */
135
+ async getObjectSchema(objectName) {
136
+ await this.connect();
137
+ try {
138
+ const schema = await this.client.meta.getObject(objectName);
139
+ return schema;
140
+ } catch (error) {
141
+ console.error(`Failed to fetch schema for ${objectName}:`, error);
142
+ throw error;
143
+ }
144
+ }
145
+ /**
146
+ * Get access to the underlying ObjectStack client for advanced operations.
147
+ */
148
+ getClient() {
149
+ return this.client;
150
+ }
151
+ };
152
+ function createObjectStackAdapter(config) {
153
+ return new ObjectStackAdapter(config);
154
+ }
155
+ export {
156
+ ObjectStackAdapter,
157
+ createObjectStackAdapter
158
+ };
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@object-ui/data-objectstack",
3
+ "version": "0.3.0",
4
+ "description": "ObjectStack Data Adapter for Object UI",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "src",
20
+ "README.md"
21
+ ],
22
+ "dependencies": {
23
+ "@objectstack/client": "^0.3.3",
24
+ "@object-ui/types": "0.3.0",
25
+ "@object-ui/core": "0.3.0"
26
+ },
27
+ "devDependencies": {
28
+ "tsup": "^8.0.1",
29
+ "typescript": "^5.3.3",
30
+ "vitest": "^1.2.0"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "scripts": {
36
+ "build": "tsup src/index.ts --format cjs,esm --dts",
37
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
38
+ "clean": "rm -rf dist",
39
+ "type-check": "tsc --noEmit",
40
+ "test": "vitest run",
41
+ "lint": "eslint ."
42
+ }
43
+ }
package/src/index.ts ADDED
@@ -0,0 +1,231 @@
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
+
9
+ import { ObjectStackClient, type QueryOptions as ObjectStackQueryOptions } from '@objectstack/client';
10
+ import type { DataSource, QueryParams, QueryResult } from '@object-ui/types';
11
+ import { convertFiltersToAST } from '@object-ui/core';
12
+
13
+ /**
14
+ * ObjectStack Data Source Adapter
15
+ *
16
+ * Bridges the ObjectStack Client SDK with the ObjectUI DataSource interface.
17
+ * This allows Object UI applications to seamlessly integrate with ObjectStack
18
+ * backends while maintaining the universal DataSource abstraction.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * import { ObjectStackAdapter } from '@object-ui/data-objectstack';
23
+ *
24
+ * const dataSource = new ObjectStackAdapter({
25
+ * baseUrl: 'https://api.example.com',
26
+ * token: 'your-api-token'
27
+ * });
28
+ *
29
+ * const users = await dataSource.find('users', {
30
+ * $filter: { status: 'active' },
31
+ * $top: 10
32
+ * });
33
+ * ```
34
+ */
35
+ export class ObjectStackAdapter<T = any> implements DataSource<T> {
36
+ private client: ObjectStackClient;
37
+ private connected: boolean = false;
38
+
39
+ constructor(config: {
40
+ baseUrl: string;
41
+ token?: string;
42
+ fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
43
+ }) {
44
+ this.client = new ObjectStackClient(config);
45
+ }
46
+
47
+ /**
48
+ * Ensure the client is connected to the server.
49
+ * Call this before making requests or it will auto-connect on first request.
50
+ */
51
+ async connect(): Promise<void> {
52
+ if (!this.connected) {
53
+ await this.client.connect();
54
+ this.connected = true;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Find multiple records with query parameters.
60
+ * Converts OData-style params to ObjectStack query options.
61
+ */
62
+ async find(resource: string, params?: QueryParams): Promise<QueryResult<T>> {
63
+ await this.connect();
64
+
65
+ const queryOptions = this.convertQueryParams(params);
66
+ const result = await this.client.data.find<T>(resource, queryOptions);
67
+
68
+ return {
69
+ data: result.value,
70
+ total: result.count,
71
+ page: params?.$skip ? Math.floor(params.$skip / (params.$top || 20)) + 1 : 1,
72
+ pageSize: params?.$top,
73
+ hasMore: result.value.length === params?.$top,
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Find a single record by ID.
79
+ */
80
+ async findOne(resource: string, id: string | number, _params?: QueryParams): Promise<T | null> {
81
+ await this.connect();
82
+
83
+ try {
84
+ const record = await this.client.data.get<T>(resource, String(id));
85
+ return record;
86
+ } catch (error) {
87
+ // If record not found, return null instead of throwing
88
+ if ((error as any)?.status === 404) {
89
+ return null;
90
+ }
91
+ throw error;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Create a new record.
97
+ */
98
+ async create(resource: string, data: Partial<T>): Promise<T> {
99
+ await this.connect();
100
+ return this.client.data.create<T>(resource, data);
101
+ }
102
+
103
+ /**
104
+ * Update an existing record.
105
+ */
106
+ async update(resource: string, id: string | number, data: Partial<T>): Promise<T> {
107
+ await this.connect();
108
+ return this.client.data.update<T>(resource, String(id), data);
109
+ }
110
+
111
+ /**
112
+ * Delete a record.
113
+ */
114
+ async delete(resource: string, id: string | number): Promise<boolean> {
115
+ await this.connect();
116
+ const result = await this.client.data.delete(resource, String(id));
117
+ return result.success;
118
+ }
119
+
120
+ /**
121
+ * Bulk operations (optional implementation).
122
+ */
123
+ async bulk(resource: string, operation: 'create' | 'update' | 'delete', data: Partial<T>[]): Promise<T[]> {
124
+ await this.connect();
125
+
126
+ switch (operation) {
127
+ case 'create':
128
+ return this.client.data.createMany<T>(resource, data);
129
+ case 'delete': {
130
+ const ids = data.map(item => (item as any).id).filter(Boolean);
131
+ await this.client.data.deleteMany(resource, ids);
132
+ return [];
133
+ }
134
+ case 'update': {
135
+ // For update, we need to handle each record individually
136
+ // or use the batch update if all records get the same changes
137
+ const results = await Promise.all(
138
+ data.map(item =>
139
+ this.client.data.update<T>(resource, String((item as any).id), item)
140
+ )
141
+ );
142
+ return results;
143
+ }
144
+ default:
145
+ throw new Error(`Unsupported bulk operation: ${operation}`);
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Convert ObjectUI QueryParams to ObjectStack QueryOptions.
151
+ * Maps OData-style conventions to ObjectStack conventions.
152
+ */
153
+ private convertQueryParams(params?: QueryParams): ObjectStackQueryOptions {
154
+ if (!params) return {};
155
+
156
+ const options: ObjectStackQueryOptions = {};
157
+
158
+ // Selection
159
+ if (params.$select) {
160
+ options.select = params.$select;
161
+ }
162
+
163
+ // Filtering - convert to ObjectStack FilterNode AST format
164
+ if (params.$filter) {
165
+ options.filters = convertFiltersToAST(params.$filter);
166
+ }
167
+
168
+ // Sorting - convert to ObjectStack format
169
+ if (params.$orderby) {
170
+ const sortArray = Object.entries(params.$orderby).map(([field, order]) => {
171
+ return order === 'desc' ? `-${field}` : field;
172
+ });
173
+ options.sort = sortArray;
174
+ }
175
+
176
+ // Pagination
177
+ if (params.$skip !== undefined) {
178
+ options.skip = params.$skip;
179
+ }
180
+
181
+ if (params.$top !== undefined) {
182
+ options.top = params.$top;
183
+ }
184
+
185
+ return options;
186
+ }
187
+
188
+ /**
189
+ * Get object schema/metadata from ObjectStack.
190
+ *
191
+ * @param objectName - Object name
192
+ * @returns Promise resolving to the object schema
193
+ */
194
+ async getObjectSchema(objectName: string): Promise<any> {
195
+ await this.connect();
196
+
197
+ try {
198
+ const schema = await this.client.meta.getObject(objectName);
199
+ return schema;
200
+ } catch (error) {
201
+ console.error(`Failed to fetch schema for ${objectName}:`, error);
202
+ throw error;
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Get access to the underlying ObjectStack client for advanced operations.
208
+ */
209
+ getClient(): ObjectStackClient {
210
+ return this.client;
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Factory function to create an ObjectStack data source.
216
+ *
217
+ * @example
218
+ * ```typescript
219
+ * const dataSource = createObjectStackAdapter({
220
+ * baseUrl: process.env.API_URL,
221
+ * token: process.env.API_TOKEN
222
+ * });
223
+ * ```
224
+ */
225
+ export function createObjectStackAdapter<T = any>(config: {
226
+ baseUrl: string;
227
+ token?: string;
228
+ fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
229
+ }): DataSource<T> {
230
+ return new ObjectStackAdapter<T>(config);
231
+ }