@objectql/sdk 3.0.1 → 4.0.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/README.md CHANGED
@@ -12,10 +12,14 @@ The `@objectql/sdk` package provides a type-safe HTTP client for ObjectQL server
12
12
  ## ✨ Features
13
13
 
14
14
  * **🌍 Universal Runtime** - Works in browsers, Node.js, Deno, and edge environments
15
- * **📦 Zero Dependencies** - Only depends on `@objectql/types` for type definitions
15
+ * **📦 Zero Dependencies** - Only depends on `@objectql/types` and `@objectstack/spec` for type definitions
16
16
  * **🔒 Type-Safe** - Full TypeScript support with generics
17
17
  * **🚀 Modern APIs** - Uses native `fetch` API available in all modern JavaScript runtimes
18
18
  * **🎯 RESTful Interface** - Clean, predictable API design
19
+ * **✅ DriverInterface v4.0** - Fully compliant with ObjectStack protocol specification
20
+ * **🔐 Authentication** - Built-in support for Bearer tokens and API keys
21
+ * **🔄 Retry Logic** - Automatic retry with exponential backoff for network resilience
22
+ * **📊 Request Logging** - Optional request/response logging for debugging
19
23
 
20
24
  ---
21
25
 
@@ -290,6 +294,274 @@ const actions = await metadataClient.listActions('users');
290
294
 
291
295
  ---
292
296
 
297
+ ### RemoteDriver (DriverInterface v4.0)
298
+
299
+ Client for connecting to a remote ObjectQL server via HTTP. Implements the standard `DriverInterface` from `@objectstack/spec`.
300
+
301
+ #### Constructor
302
+
303
+ ```typescript
304
+ // Legacy constructor
305
+ new RemoteDriver(baseUrl: string, rpcPath?: string)
306
+
307
+ // New config-based constructor (recommended)
308
+ new RemoteDriver(config: SdkConfig)
309
+ ```
310
+
311
+ **Config Options:**
312
+ * `baseUrl` (string, required) - Base URL of the ObjectQL server
313
+ * `rpcPath` (string, optional) - JSON-RPC endpoint path (default: /api/objectql)
314
+ * `queryPath` (string, optional) - Query endpoint path (default: /api/query)
315
+ * `commandPath` (string, optional) - Command endpoint path (default: /api/command)
316
+ * `executePath` (string, optional) - Custom execute endpoint path (default: /api/execute)
317
+ * `token` (string, optional) - Authentication token (Bearer)
318
+ * `apiKey` (string, optional) - API key for authentication
319
+ * `headers` (Record<string, string>, optional) - Custom HTTP headers
320
+ * `timeout` (number, optional) - Request timeout in milliseconds (default: 30000)
321
+ * `enableRetry` (boolean, optional) - Enable automatic retry on failure (default: false)
322
+ * `maxRetries` (number, optional) - Maximum retry attempts (default: 3)
323
+ * `enableLogging` (boolean, optional) - Enable request/response logging (default: false)
324
+
325
+ #### Methods
326
+
327
+ ##### `executeQuery(ast: QueryAST, options?: any): Promise<{ value: any[]; count?: number }>`
328
+
329
+ Execute a query using QueryAST format (DriverInterface v4.0).
330
+
331
+ ```typescript
332
+ import { RemoteDriver } from '@objectql/sdk';
333
+
334
+ const driver = new RemoteDriver({
335
+ baseUrl: 'http://localhost:3000',
336
+ token: 'your-auth-token',
337
+ enableRetry: true,
338
+ maxRetries: 3
339
+ });
340
+
341
+ // Execute a QueryAST
342
+ const result = await driver.executeQuery({
343
+ object: 'users',
344
+ fields: ['name', 'email', 'status'],
345
+ filters: {
346
+ type: 'comparison',
347
+ field: 'status',
348
+ operator: '=',
349
+ value: 'active'
350
+ },
351
+ sort: [{ field: 'created_at', order: 'desc' }],
352
+ top: 10,
353
+ skip: 0
354
+ });
355
+
356
+ console.log(result.value); // Array of users
357
+ console.log(result.count); // Total count
358
+ ```
359
+
360
+ ##### `executeCommand(command: Command, options?: any): Promise<CommandResult>`
361
+
362
+ Execute a command for mutation operations (create, update, delete, bulk operations).
363
+
364
+ ```typescript
365
+ // Create a record
366
+ const createResult = await driver.executeCommand({
367
+ type: 'create',
368
+ object: 'users',
369
+ data: {
370
+ name: 'Alice',
371
+ email: 'alice@example.com',
372
+ status: 'active'
373
+ }
374
+ });
375
+
376
+ console.log(createResult.success); // true
377
+ console.log(createResult.data); // Created record
378
+ console.log(createResult.affected); // 1
379
+
380
+ // Update a record
381
+ const updateResult = await driver.executeCommand({
382
+ type: 'update',
383
+ object: 'users',
384
+ id: 'user_123',
385
+ data: { status: 'inactive' }
386
+ });
387
+
388
+ // Delete a record
389
+ const deleteResult = await driver.executeCommand({
390
+ type: 'delete',
391
+ object: 'users',
392
+ id: 'user_123'
393
+ });
394
+
395
+ // Bulk create
396
+ const bulkCreateResult = await driver.executeCommand({
397
+ type: 'bulkCreate',
398
+ object: 'users',
399
+ records: [
400
+ { name: 'Bob', email: 'bob@example.com' },
401
+ { name: 'Charlie', email: 'charlie@example.com' }
402
+ ]
403
+ });
404
+
405
+ // Bulk update
406
+ const bulkUpdateResult = await driver.executeCommand({
407
+ type: 'bulkUpdate',
408
+ object: 'users',
409
+ updates: [
410
+ { id: 'user_1', data: { status: 'active' } },
411
+ { id: 'user_2', data: { status: 'inactive' } }
412
+ ]
413
+ });
414
+
415
+ // Bulk delete
416
+ const bulkDeleteResult = await driver.executeCommand({
417
+ type: 'bulkDelete',
418
+ object: 'users',
419
+ ids: ['user_1', 'user_2', 'user_3']
420
+ });
421
+ ```
422
+
423
+ ##### `execute(endpoint?: string, payload?: any, options?: any): Promise<any>`
424
+
425
+ Execute a custom operation on the remote server.
426
+
427
+ ```typescript
428
+ // Execute a custom workflow
429
+ const workflowResult = await driver.execute('/api/workflows/approve', {
430
+ workflowId: 'wf_123',
431
+ comment: 'Approved by manager'
432
+ });
433
+
434
+ // Use default execute endpoint
435
+ const customResult = await driver.execute(undefined, {
436
+ action: 'calculateMetrics',
437
+ params: { year: 2024, quarter: 'Q1' }
438
+ });
439
+
440
+ // Call a custom action
441
+ const actionResult = await driver.execute('/api/actions/send-email', {
442
+ to: 'user@example.com',
443
+ subject: 'Welcome',
444
+ body: 'Welcome to our platform!'
445
+ });
446
+ ```
447
+
448
+ #### Legacy CRUD Methods
449
+
450
+ The RemoteDriver also supports legacy CRUD methods for backward compatibility:
451
+
452
+ ```typescript
453
+ // Find records
454
+ const users = await driver.find('users', {
455
+ filters: [['status', '=', 'active']],
456
+ sort: [['name', 'asc']],
457
+ limit: 10
458
+ });
459
+
460
+ // Find one record
461
+ const user = await driver.findOne('users', 'user_123');
462
+
463
+ // Create a record
464
+ const newUser = await driver.create('users', {
465
+ name: 'Alice',
466
+ email: 'alice@example.com'
467
+ });
468
+
469
+ // Update a record
470
+ const updated = await driver.update('users', 'user_123', {
471
+ status: 'inactive'
472
+ });
473
+
474
+ // Delete a record
475
+ await driver.delete('users', 'user_123');
476
+
477
+ // Count records
478
+ const count = await driver.count('users', {
479
+ filters: [['status', '=', 'active']]
480
+ });
481
+ ```
482
+
483
+ ### Authentication Examples
484
+
485
+ ```typescript
486
+ // Bearer token authentication
487
+ const driverWithToken = new RemoteDriver({
488
+ baseUrl: 'http://localhost:3000',
489
+ token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
490
+ });
491
+
492
+ // API key authentication
493
+ const driverWithApiKey = new RemoteDriver({
494
+ baseUrl: 'http://localhost:3000',
495
+ apiKey: 'sk-1234567890abcdef'
496
+ });
497
+
498
+ // Both token and API key
499
+ const driverWithBoth = new RemoteDriver({
500
+ baseUrl: 'http://localhost:3000',
501
+ token: 'jwt-token',
502
+ apiKey: 'api-key'
503
+ });
504
+
505
+ // Custom headers
506
+ const driverWithHeaders = new RemoteDriver({
507
+ baseUrl: 'http://localhost:3000',
508
+ headers: {
509
+ 'X-Tenant-ID': 'tenant_123',
510
+ 'X-Request-ID': crypto.randomUUID()
511
+ }
512
+ });
513
+ ```
514
+
515
+ ### Error Handling and Retry
516
+
517
+ ```typescript
518
+ import { ObjectQLError, ApiErrorCode } from '@objectql/types';
519
+
520
+ // Enable retry with exponential backoff
521
+ const driver = new RemoteDriver({
522
+ baseUrl: 'http://localhost:3000',
523
+ enableRetry: true,
524
+ maxRetries: 3,
525
+ timeout: 10000
526
+ });
527
+
528
+ try {
529
+ const result = await driver.executeQuery({ object: 'users' });
530
+ } catch (error) {
531
+ if (error instanceof ObjectQLError) {
532
+ switch (error.code) {
533
+ case ApiErrorCode.UNAUTHORIZED:
534
+ console.error('Authentication required');
535
+ break;
536
+ case ApiErrorCode.VALIDATION_ERROR:
537
+ console.error('Validation failed:', error.details);
538
+ break;
539
+ case ApiErrorCode.NOT_FOUND:
540
+ console.error('Resource not found');
541
+ break;
542
+ default:
543
+ console.error('API error:', error.message);
544
+ }
545
+ }
546
+ }
547
+ ```
548
+
549
+ ### Request Logging
550
+
551
+ ```typescript
552
+ // Enable logging for debugging
553
+ const driver = new RemoteDriver({
554
+ baseUrl: 'http://localhost:3000',
555
+ enableLogging: true
556
+ });
557
+
558
+ // Logs will be printed to console:
559
+ // [RemoteDriver] executeQuery { endpoint: '...', ast: {...} }
560
+ // [RemoteDriver] executeQuery response { value: [...], count: 10 }
561
+ ```
562
+
563
+ ---
564
+
293
565
  ## 🌐 Browser Compatibility
294
566
 
295
567
  The SDK uses modern JavaScript APIs available in all current browsers:
package/dist/index.d.ts CHANGED
@@ -1,3 +1,10 @@
1
+ /**
2
+ * ObjectQL
3
+ * Copyright (c) 2026-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
+ */
1
8
  /**
2
9
  * @objectql/sdk - Universal HTTP Client for ObjectQL
3
10
  *
@@ -14,15 +21,226 @@
14
21
  *
15
22
  * @packageDocumentation
16
23
  */
17
- import { Driver, IDataApiClient, IMetadataApiClient, DataApiClientConfig, MetadataApiClientConfig, DataApiListParams, DataApiListResponse, DataApiItemResponse, DataApiCreateRequest, DataApiCreateManyRequest, DataApiUpdateRequest, DataApiBulkUpdateRequest, DataApiBulkDeleteRequest, DataApiCountResponse, DataApiDeleteResponse, DataApiResponse, MetadataApiObjectListResponse, MetadataApiObjectDetailResponse, FieldMetadataResponse, MetadataApiActionsResponse, MetadataApiListResponse, MetadataApiResponse, FilterExpression } from '@objectql/types';
24
+ import { Driver, IDataApiClient, IMetadataApiClient, DataApiClientConfig, MetadataApiClientConfig, DataApiListParams, DataApiListResponse, DataApiItemResponse, DataApiCreateRequest, DataApiCreateManyRequest, DataApiUpdateRequest, DataApiBulkUpdateRequest, DataApiBulkDeleteRequest, DataApiCountResponse, DataApiDeleteResponse, DataApiResponse, MetadataApiObjectListResponse, MetadataApiObjectDetailResponse, FieldMetadataResponse, MetadataApiActionsResponse, MetadataApiListResponse, MetadataApiResponse, Filter } from '@objectql/types';
25
+ import { DriverInterface, QueryAST } from '@objectstack/spec';
26
+ /**
27
+ * Command interface for executeCommand method
28
+ * Defines the structure of mutation commands sent to the remote API
29
+ */
30
+ export interface Command {
31
+ type: 'create' | 'update' | 'delete' | 'bulkCreate' | 'bulkUpdate' | 'bulkDelete';
32
+ object: string;
33
+ data?: any;
34
+ id?: string | number;
35
+ ids?: Array<string | number>;
36
+ records?: any[];
37
+ updates?: Array<{
38
+ id: string | number;
39
+ data: any;
40
+ }>;
41
+ options?: any;
42
+ }
43
+ /**
44
+ * Command result interface
45
+ * Standard response format for command execution
46
+ */
47
+ export interface CommandResult {
48
+ success: boolean;
49
+ data?: any;
50
+ affected: number;
51
+ error?: string;
52
+ }
53
+ /**
54
+ * SDK Configuration for the RemoteDriver
55
+ */
56
+ export interface SdkConfig {
57
+ /** Base URL of the remote ObjectQL server */
58
+ baseUrl: string;
59
+ /** RPC endpoint path (default: /api/objectql) */
60
+ rpcPath?: string;
61
+ /** Query endpoint path (default: /api/query) */
62
+ queryPath?: string;
63
+ /** Command endpoint path (default: /api/command) */
64
+ commandPath?: string;
65
+ /** Custom execute endpoint path (default: /api/execute) */
66
+ executePath?: string;
67
+ /** Authentication token */
68
+ token?: string;
69
+ /** API key for authentication */
70
+ apiKey?: string;
71
+ /** Custom headers */
72
+ headers?: Record<string, string>;
73
+ /** Request timeout in milliseconds (default: 30000) */
74
+ timeout?: number;
75
+ /** Enable retry on failure (default: false) */
76
+ enableRetry?: boolean;
77
+ /** Maximum number of retry attempts (default: 3) */
78
+ maxRetries?: number;
79
+ /** Enable request/response logging (default: false) */
80
+ enableLogging?: boolean;
81
+ }
18
82
  /**
19
83
  * Legacy Driver implementation that uses JSON-RPC style API
84
+ *
85
+ * Implements both the legacy Driver interface from @objectql/types and
86
+ * the standard DriverInterface from @objectstack/spec for compatibility
87
+ * with the new kernel-based plugin system.
88
+ *
89
+ * @version 4.0.0 - DriverInterface compliant
20
90
  */
21
- export declare class RemoteDriver implements Driver {
22
- private baseUrl;
91
+ export declare class RemoteDriver implements Driver, DriverInterface {
92
+ readonly name = "RemoteDriver";
93
+ readonly version = "4.0.0";
94
+ readonly supports: {
95
+ transactions: boolean;
96
+ joins: boolean;
97
+ fullTextSearch: boolean;
98
+ jsonFields: boolean;
99
+ arrayFields: boolean;
100
+ };
23
101
  private rpcPath;
24
- constructor(baseUrl: string, rpcPath?: string);
102
+ private queryPath;
103
+ private commandPath;
104
+ private executePath;
105
+ private baseUrl;
106
+ private token?;
107
+ private apiKey?;
108
+ private headers;
109
+ private timeout;
110
+ private enableRetry;
111
+ private maxRetries;
112
+ private enableLogging;
113
+ constructor(baseUrlOrConfig: string | SdkConfig, rpcPath?: string);
114
+ /**
115
+ * Build full endpoint URL
116
+ * @private
117
+ */
118
+ private buildEndpoint;
119
+ /**
120
+ * Get authentication headers
121
+ * @private
122
+ */
123
+ private getAuthHeaders;
124
+ /**
125
+ * Handle HTTP errors and convert to ObjectQLError
126
+ * @private
127
+ */
128
+ private handleHttpError;
129
+ /**
130
+ * Retry logic with exponential backoff
131
+ * @private
132
+ */
133
+ private retryWithBackoff;
134
+ /**
135
+ * Log request/response if logging is enabled
136
+ * @private
137
+ */
138
+ private log;
139
+ /**
140
+ * Connect to the remote server (for DriverInterface compatibility)
141
+ */
142
+ connect(): Promise<void>;
143
+ /**
144
+ * Check remote server connection health
145
+ */
146
+ checkHealth(): Promise<boolean>;
147
+ /**
148
+ * Execute a query using QueryAST format (DriverInterface v4.0)
149
+ *
150
+ * Sends a QueryAST to the remote server's /api/query endpoint
151
+ * and returns the query results.
152
+ *
153
+ * @param ast - The QueryAST to execute
154
+ * @param options - Optional execution options
155
+ * @returns Query result with value array and optional count
156
+ *
157
+ * @example
158
+ * ```typescript
159
+ * const result = await driver.executeQuery({
160
+ * object: 'users',
161
+ * fields: ['name', 'email'],
162
+ * filters: {
163
+ * type: 'comparison',
164
+ * field: 'status',
165
+ * operator: '=',
166
+ * value: 'active'
167
+ * },
168
+ * sort: [{ field: 'created_at', order: 'desc' }],
169
+ * top: 10
170
+ * });
171
+ * ```
172
+ */
173
+ executeQuery(ast: QueryAST, options?: any): Promise<{
174
+ value: any[];
175
+ count?: number;
176
+ }>;
177
+ /**
178
+ * Execute a command using Command format (DriverInterface v4.0)
179
+ *
180
+ * Sends a Command to the remote server's /api/command endpoint
181
+ * for mutation operations (create, update, delete, bulk operations).
182
+ *
183
+ * @param command - The command to execute
184
+ * @param options - Optional execution options
185
+ * @returns Command execution result
186
+ *
187
+ * @example
188
+ * ```typescript
189
+ * // Create a record
190
+ * const result = await driver.executeCommand({
191
+ * type: 'create',
192
+ * object: 'users',
193
+ * data: { name: 'Alice', email: 'alice@example.com' }
194
+ * });
195
+ *
196
+ * // Bulk update
197
+ * const bulkResult = await driver.executeCommand({
198
+ * type: 'bulkUpdate',
199
+ * object: 'users',
200
+ * updates: [
201
+ * { id: '1', data: { status: 'active' } },
202
+ * { id: '2', data: { status: 'inactive' } }
203
+ * ]
204
+ * });
205
+ * ```
206
+ */
207
+ executeCommand(command: Command, options?: any): Promise<CommandResult>;
208
+ /**
209
+ * Execute a custom operation on the remote server
210
+ *
211
+ * Allows calling custom HTTP endpoints with flexible parameters.
212
+ * Useful for custom actions, workflows, or specialized operations.
213
+ *
214
+ * @param endpoint - Optional custom endpoint path (defaults to /api/execute)
215
+ * @param payload - Request payload
216
+ * @param options - Optional execution options
217
+ * @returns Execution result
218
+ *
219
+ * @example
220
+ * ```typescript
221
+ * // Execute a custom workflow
222
+ * const result = await driver.execute('/api/workflows/approve', {
223
+ * workflowId: 'wf_123',
224
+ * comment: 'Approved'
225
+ * });
226
+ *
227
+ * // Use default execute endpoint
228
+ * const result = await driver.execute(undefined, {
229
+ * action: 'calculateMetrics',
230
+ * params: { year: 2024 }
231
+ * });
232
+ * ```
233
+ */
234
+ execute(endpoint?: string, payload?: any, options?: any): Promise<any>;
25
235
  private request;
236
+ /**
237
+ * Normalizes query format to support both legacy UnifiedQuery and QueryAST formats.
238
+ * This ensures backward compatibility while supporting the new @objectstack/spec interface.
239
+ *
240
+ * QueryAST format uses 'top' for limit, while UnifiedQuery uses 'limit'.
241
+ * QueryAST sort is array of {field, order}, while UnifiedQuery is array of [field, order].
242
+ */
243
+ private normalizeQuery;
26
244
  find(objectName: string, query: any, options?: any): Promise<any[]>;
27
245
  findOne(objectName: string, id: string | number, query?: any, options?: any): Promise<any>;
28
246
  create(objectName: string, data: any, options?: any): Promise<any>;
@@ -79,7 +297,7 @@ export declare class DataApiClient implements IDataApiClient {
79
297
  updateMany(objectName: string, request: DataApiBulkUpdateRequest): Promise<DataApiResponse>;
80
298
  delete(objectName: string, id: string | number): Promise<DataApiDeleteResponse>;
81
299
  deleteMany(objectName: string, request: DataApiBulkDeleteRequest): Promise<DataApiDeleteResponse>;
82
- count(objectName: string, filters?: FilterExpression): Promise<DataApiCountResponse>;
300
+ count(objectName: string, filters?: Filter): Promise<DataApiCountResponse>;
83
301
  }
84
302
  /**
85
303
  * REST-based Metadata API Client