@objectstack/client 1.1.0 → 2.0.1

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
@@ -11,6 +11,8 @@ The official TypeScript client for ObjectStack.
11
11
  - **Batch Operations**: Efficient bulk create/update/delete with transaction support.
12
12
  - **View Storage**: Save, load, and share custom UI view configurations.
13
13
  - **Standardized Errors**: Machine-readable error codes with retry guidance.
14
+ - **Full Protocol Coverage**: Implements all 13 API namespaces defined in `@objectstack/spec`
15
+ - **95+ Methods**: Complete implementation of discovery, metadata, data, auth, workflow, realtime, AI, and more.
14
16
 
15
17
  ## 🤖 AI Development Context
16
18
 
@@ -219,3 +221,130 @@ const data = await retryableRequest(() =>
219
221
  );
220
222
  ```
221
223
 
224
+ ## Protocol Compliance
225
+
226
+ The `@objectstack/client` SDK implements all 13 API namespaces defined in the `@objectstack/spec` protocol specification:
227
+
228
+ | Namespace | Purpose | Status |
229
+ |-----------|---------|:------:|
230
+ | `discovery` | API version & capabilities detection | ✅ |
231
+ | `meta` | Metadata operations (objects, plugins, etc.) | ✅ |
232
+ | `data` | CRUD & query operations | ✅ |
233
+ | `auth` | Authentication & user management | ✅ |
234
+ | `packages` | Plugin/package lifecycle management | ✅ |
235
+ | `views` | UI view definitions | ✅ |
236
+ | `workflow` | Workflow state transitions | ✅ |
237
+ | `analytics` | Analytics queries | ✅ |
238
+ | `automation` | Automation triggers | ✅ |
239
+ | `i18n` | Internationalization | ✅ |
240
+ | `notifications` | Push notifications | ✅ |
241
+ | `realtime` | WebSocket subscriptions | ✅ |
242
+ | `ai` | AI services (NLQ, chat, insights) | ✅ |
243
+
244
+ For detailed compliance verification, see [CLIENT_SPEC_COMPLIANCE.md](./CLIENT_SPEC_COMPLIANCE.md).
245
+
246
+ ## Available Namespaces
247
+
248
+ ### Complete API Coverage
249
+
250
+ ```typescript
251
+ const client = new ObjectStackClient({ baseUrl: 'http://localhost:3000' });
252
+ await client.connect();
253
+
254
+ // Discovery & Metadata
255
+ await client.meta.getTypes(); // List metadata types
256
+ await client.meta.getItems('object'); // List all objects
257
+ await client.meta.getItem('object', 'contact'); // Get specific object
258
+
259
+ // Data Operations
260
+ await client.data.find('contact', { filters: { status: 'active' } });
261
+ await client.data.create('contact', { name: 'John' });
262
+ await client.data.update('contact', id, { status: 'inactive' });
263
+ await client.data.delete('contact', id);
264
+ await client.data.batch('contact', batchRequest);
265
+
266
+ // Authentication
267
+ await client.auth.login({ email: 'user@example.com', password: 'pass' });
268
+ await client.auth.register({ email: 'new@example.com', password: 'pass' });
269
+ await client.auth.me();
270
+ await client.auth.logout();
271
+ await client.auth.refreshToken('refresh-token-string');
272
+
273
+ // Package Management
274
+ await client.packages.list();
275
+ await client.packages.install({
276
+ name: 'vendor_plugin',
277
+ label: 'Vendor Plugin',
278
+ version: '1.0.0',
279
+ });
280
+ await client.packages.enable('plugin-id');
281
+
282
+ // Permissions
283
+ await client.permissions.check({ object: 'contact', action: 'create' });
284
+ await client.permissions.getObjectPermissions('contact');
285
+ await client.permissions.getEffectivePermissions();
286
+
287
+ // Workflow
288
+ await client.workflow.getConfig('approval');
289
+ await client.workflow.getState('approval', recordId);
290
+ await client.workflow.transition({ object: 'approval', recordId, transition: 'submit' });
291
+ await client.workflow.approve({ object: 'approval', recordId });
292
+ await client.workflow.reject({ object: 'approval', recordId, reason: 'Incomplete' });
293
+
294
+ // Realtime
295
+ await client.realtime.connect({ protocol: 'websocket' });
296
+ await client.realtime.subscribe({ channel: 'contact', event: 'update' });
297
+ await client.realtime.setPresence({ status: 'online' });
298
+
299
+ // Notifications
300
+ await client.notifications.registerDevice({ token: 'device-token', platform: 'ios' });
301
+ await client.notifications.list({ unreadOnly: true });
302
+ await client.notifications.markRead(['notif-1', 'notif-2']);
303
+
304
+ // AI Services
305
+ await client.ai.nlq({ query: 'Show me all active contacts' });
306
+ await client.ai.chat({ message: 'Summarize this project', context: projectId });
307
+ await client.ai.suggest({ object: 'contact', field: 'industry' });
308
+ await client.ai.insights({ object: 'sales', recordId: dealId });
309
+
310
+ // Internationalization
311
+ await client.i18n.getLocales();
312
+ await client.i18n.getTranslations('zh-CN');
313
+ await client.i18n.getFieldLabels('contact', 'zh-CN');
314
+
315
+ // Analytics
316
+ await client.analytics.query({ object: 'sales', aggregations: ['sum:amount'] });
317
+ await client.analytics.meta('sales');
318
+
319
+ // Automation
320
+ await client.automation.trigger('send_welcome_email', { userId });
321
+
322
+ // File Storage
323
+ await client.storage.upload(fileData, 'user');
324
+ await client.storage.getDownloadUrl('file-123');
325
+ ```
326
+
327
+ ## Testing
328
+
329
+ ### Unit Tests
330
+
331
+ ```bash
332
+ pnpm test
333
+ ```
334
+
335
+ ### Integration Tests
336
+
337
+ **Note:** Integration tests require a running ObjectStack server. The server is provided by a separate repository and must be set up independently.
338
+
339
+ ```bash
340
+ # Start test server (in the ObjectStack server repository)
341
+ # Follow that project's documentation for test server setup
342
+ # Example: cd /path/to/objectstack-server && pnpm dev:test
343
+
344
+ # Run integration tests (in this repository)
345
+ cd packages/client
346
+ pnpm test:integration
347
+ ```
348
+
349
+ See [CLIENT_SERVER_INTEGRATION_TESTS.md](./CLIENT_SERVER_INTEGRATION_TESTS.md) for detailed test specifications.
350
+
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { FilterCondition, QueryAST, SortNode, AggregationNode } from '@objectstack/spec/data';
2
- import { GetDiscoveryResponse, StandardErrorCode, ErrorCategory, GetMetaTypesResponse, GetMetaItemsResponse, MetadataCacheRequest, MetadataCacheResponse, LoginRequest, SessionResponse, FileUploadResponse, BatchUpdateRequest, BatchUpdateResponse, BatchOptions } from '@objectstack/spec/api';
3
- export { BatchOperationResult, BatchOptions, BatchRecord, BatchUpdateRequest, BatchUpdateResponse, DeleteManyRequest, ErrorCategory, GetDiscoveryResponse, GetMetaItemsResponse, GetMetaTypesResponse, MetadataCacheRequest, MetadataCacheResponse, StandardErrorCode, UpdateManyRequest } from '@objectstack/spec/api';
2
+ import { GetDiscoveryResponse, StandardErrorCode, ErrorCategory, GetMetaTypesResponse, GetMetaItemsResponse, MetadataCacheRequest, MetadataCacheResponse, LoginRequest, SessionResponse, RegisterRequest, FileUploadResponse, CheckPermissionRequest, CheckPermissionResponse, GetObjectPermissionsResponse, GetEffectivePermissionsResponse, RealtimeConnectRequest, RealtimeConnectResponse, RealtimeSubscribeRequest, RealtimeSubscribeResponse, SetPresenceRequest, GetPresenceResponse, GetWorkflowConfigResponse, GetWorkflowStateResponse, WorkflowTransitionRequest, WorkflowTransitionResponse, WorkflowApproveRequest, WorkflowApproveResponse, WorkflowRejectRequest, WorkflowRejectResponse, ListViewsResponse, GetViewResponse, CreateViewRequest, CreateViewResponse, UpdateViewRequest, UpdateViewResponse, DeleteViewResponse, RegisterDeviceRequest, RegisterDeviceResponse, UnregisterDeviceResponse, GetNotificationPreferencesResponse, UpdateNotificationPreferencesRequest, UpdateNotificationPreferencesResponse, ListNotificationsResponse, MarkNotificationsReadResponse, MarkAllNotificationsReadResponse, AiNlqRequest, AiNlqResponse, AiChatRequest, AiChatResponse, AiSuggestRequest, AiSuggestResponse, AiInsightsRequest, AiInsightsResponse, GetLocalesResponse, GetTranslationsResponse, GetFieldLabelsResponse, BatchUpdateRequest, BatchUpdateResponse, BatchOptions } from '@objectstack/spec/api';
3
+ export { AiChatRequest, AiChatResponse, AiInsightsRequest, AiInsightsResponse, AiNlqRequest, AiNlqResponse, AiSuggestRequest, AiSuggestResponse, BatchOperationResult, BatchOptions, BatchRecord, BatchUpdateRequest, BatchUpdateResponse, CheckPermissionRequest, CheckPermissionResponse, CreateViewResponse, DeleteManyRequest, DeleteViewResponse, ErrorCategory, GetDiscoveryResponse, GetEffectivePermissionsResponse, GetFieldLabelsResponse, GetLocalesResponse, GetMetaItemsResponse, GetMetaTypesResponse, GetObjectPermissionsResponse, GetPresenceResponse, GetTranslationsResponse, GetViewResponse, GetWorkflowConfigResponse, GetWorkflowStateResponse, ListNotificationsResponse, ListViewsResponse, MetadataCacheRequest, MetadataCacheResponse, RealtimeConnectRequest, RealtimeConnectResponse, RealtimeSubscribeRequest, RealtimeSubscribeResponse, RefreshTokenRequest, RegisterDeviceRequest, RegisterDeviceResponse, RegisterRequest, StandardErrorCode, UpdateManyRequest, UpdateViewResponse, WorkflowApproveRequest, WorkflowApproveResponse, WorkflowRejectRequest, WorkflowRejectResponse, WorkflowTransitionRequest, WorkflowTransitionResponse } from '@objectstack/spec/api';
4
4
  import { Logger } from '@objectstack/core';
5
5
 
6
6
  /**
@@ -62,6 +62,26 @@ declare class FilterBuilder<T = any> {
62
62
  * IS NOT NULL filter: field IS NOT NULL
63
63
  */
64
64
  isNotNull<K extends keyof T>(field: K): this;
65
+ /**
66
+ * BETWEEN filter: field BETWEEN min AND max
67
+ */
68
+ between<K extends keyof T>(field: K, min: T[K], max: T[K]): this;
69
+ /**
70
+ * CONTAINS filter: field contains value (case-insensitive LIKE %value%)
71
+ */
72
+ contains<K extends keyof T>(field: K, value: string): this;
73
+ /**
74
+ * STARTS WITH filter: field starts with value (LIKE value%)
75
+ */
76
+ startsWith<K extends keyof T>(field: K, value: string): this;
77
+ /**
78
+ * ENDS WITH filter: field ends with value (LIKE %value)
79
+ */
80
+ endsWith<K extends keyof T>(field: K, value: string): this;
81
+ /**
82
+ * EXISTS filter: field is not null (alias for isNotNull)
83
+ */
84
+ exists<K extends keyof T>(field: K): this;
65
85
  /**
66
86
  * Build the filter condition
67
87
  */
@@ -110,6 +130,25 @@ declare class QueryBuilder<T = any> {
110
130
  * Group by fields
111
131
  */
112
132
  groupBy<K extends keyof T>(...fields: K[]): this;
133
+ /**
134
+ * Expand (eager-load) a related object with an optional sub-query
135
+ */
136
+ expand(relation: string, subQuery?: Partial<QueryAST>): this;
137
+ /**
138
+ * Add full-text search
139
+ */
140
+ search(query: string, options?: {
141
+ fields?: string[];
142
+ fuzzy?: boolean;
143
+ }): this;
144
+ /**
145
+ * Set cursor for keyset pagination
146
+ */
147
+ cursor(cursor: Record<string, any>): this;
148
+ /**
149
+ * Enable SELECT DISTINCT
150
+ */
151
+ distinct(): this;
113
152
  /**
114
153
  * Build the final query AST
115
154
  */
@@ -221,20 +260,28 @@ declare class ObjectStackClient {
221
260
  search: boolean;
222
261
  files: boolean;
223
262
  graphql: boolean;
263
+ notifications: boolean;
224
264
  analytics: boolean;
225
- hub: boolean;
265
+ ai: boolean;
266
+ i18n: boolean;
267
+ workflow: boolean;
226
268
  websockets: boolean;
227
269
  } | undefined;
228
270
  endpoints?: {
229
271
  data: string;
230
272
  metadata: string;
231
- auth: string;
232
273
  graphql?: string | undefined;
274
+ notifications?: string | undefined;
233
275
  storage?: string | undefined;
276
+ auth?: string | undefined;
234
277
  automation?: string | undefined;
235
278
  analytics?: string | undefined;
236
- hub?: string | undefined;
279
+ realtime?: string | undefined;
280
+ ai?: string | undefined;
281
+ i18n?: string | undefined;
237
282
  ui?: string | undefined;
283
+ workflow?: string | undefined;
284
+ packages?: string | undefined;
238
285
  } | undefined;
239
286
  }>;
240
287
  /**
@@ -288,18 +335,6 @@ declare class ObjectStackClient {
288
335
  meta: (cube: string) => Promise<any>;
289
336
  explain: (payload: any) => Promise<any>;
290
337
  };
291
- /**
292
- * Hub Management Services
293
- */
294
- hub: {
295
- spaces: {
296
- list: () => Promise<any>;
297
- create: (payload: any) => Promise<any>;
298
- };
299
- plugins: {
300
- install: (pkg: string, version?: string) => Promise<any>;
301
- };
302
- };
303
338
  /**
304
339
  * Package Management Services
305
340
  *
@@ -374,6 +409,14 @@ declare class ObjectStackClient {
374
409
  login: (request: LoginRequest) => Promise<SessionResponse>;
375
410
  logout: () => Promise<void>;
376
411
  me: () => Promise<SessionResponse>;
412
+ /**
413
+ * Register a new user account
414
+ */
415
+ register: (request: RegisterRequest) => Promise<SessionResponse>;
416
+ /**
417
+ * Refresh an authentication token
418
+ */
419
+ refreshToken: (refreshToken: string) => Promise<SessionResponse>;
377
420
  };
378
421
  /**
379
422
  * Storage Services
@@ -388,6 +431,181 @@ declare class ObjectStackClient {
388
431
  automation: {
389
432
  trigger: (triggerName: string, payload: any) => Promise<any>;
390
433
  };
434
+ /**
435
+ * Permissions Services
436
+ */
437
+ permissions: {
438
+ /**
439
+ * Check if current user has permission for an action on an object
440
+ */
441
+ check: (request: CheckPermissionRequest) => Promise<CheckPermissionResponse>;
442
+ /**
443
+ * Get all permissions for a specific object
444
+ */
445
+ getObjectPermissions: (object: string) => Promise<GetObjectPermissionsResponse>;
446
+ /**
447
+ * Get effective permissions for the current user
448
+ */
449
+ getEffectivePermissions: () => Promise<GetEffectivePermissionsResponse>;
450
+ };
451
+ /**
452
+ * Realtime Services
453
+ */
454
+ realtime: {
455
+ /**
456
+ * Establish a realtime connection
457
+ */
458
+ connect: (request?: RealtimeConnectRequest) => Promise<RealtimeConnectResponse>;
459
+ /**
460
+ * Disconnect from realtime services
461
+ */
462
+ disconnect: () => Promise<void>;
463
+ /**
464
+ * Subscribe to a channel
465
+ */
466
+ subscribe: (request: RealtimeSubscribeRequest) => Promise<RealtimeSubscribeResponse>;
467
+ /**
468
+ * Unsubscribe from a channel
469
+ */
470
+ unsubscribe: (subscriptionId: string) => Promise<void>;
471
+ /**
472
+ * Set presence state on a channel
473
+ */
474
+ setPresence: (channel: string, state: SetPresenceRequest["state"]) => Promise<void>;
475
+ /**
476
+ * Get presence information for a channel
477
+ */
478
+ getPresence: (channel: string) => Promise<GetPresenceResponse>;
479
+ };
480
+ /**
481
+ * Workflow Services
482
+ */
483
+ workflow: {
484
+ /**
485
+ * Get workflow configuration for an object
486
+ */
487
+ getConfig: (object: string) => Promise<GetWorkflowConfigResponse>;
488
+ /**
489
+ * Get current workflow state for a record
490
+ */
491
+ getState: (object: string, recordId: string) => Promise<GetWorkflowStateResponse>;
492
+ /**
493
+ * Execute a workflow state transition
494
+ */
495
+ transition: (request: WorkflowTransitionRequest) => Promise<WorkflowTransitionResponse>;
496
+ /**
497
+ * Approve a workflow step
498
+ */
499
+ approve: (request: WorkflowApproveRequest) => Promise<WorkflowApproveResponse>;
500
+ /**
501
+ * Reject a workflow step
502
+ */
503
+ reject: (request: WorkflowRejectRequest) => Promise<WorkflowRejectResponse>;
504
+ };
505
+ /**
506
+ * Views CRUD Services
507
+ */
508
+ views: {
509
+ /**
510
+ * List views for an object
511
+ */
512
+ list: (object: string, type?: "list" | "form") => Promise<ListViewsResponse>;
513
+ /**
514
+ * Get a specific view
515
+ */
516
+ get: (object: string, viewId: string) => Promise<GetViewResponse>;
517
+ /**
518
+ * Create a new view
519
+ */
520
+ create: (object: string, data: CreateViewRequest["data"]) => Promise<CreateViewResponse>;
521
+ /**
522
+ * Update an existing view
523
+ */
524
+ update: (object: string, viewId: string, data: UpdateViewRequest["data"]) => Promise<UpdateViewResponse>;
525
+ /**
526
+ * Delete a view
527
+ */
528
+ delete: (object: string, viewId: string) => Promise<DeleteViewResponse>;
529
+ };
530
+ /**
531
+ * Notification Services
532
+ */
533
+ notifications: {
534
+ /**
535
+ * Register a device for push notifications
536
+ */
537
+ registerDevice: (request: RegisterDeviceRequest) => Promise<RegisterDeviceResponse>;
538
+ /**
539
+ * Unregister a device from push notifications
540
+ */
541
+ unregisterDevice: (deviceId: string) => Promise<UnregisterDeviceResponse>;
542
+ /**
543
+ * Get notification preferences for the current user
544
+ */
545
+ getPreferences: () => Promise<GetNotificationPreferencesResponse>;
546
+ /**
547
+ * Update notification preferences
548
+ */
549
+ updatePreferences: (preferences: UpdateNotificationPreferencesRequest["preferences"]) => Promise<UpdateNotificationPreferencesResponse>;
550
+ /**
551
+ * List notifications for the current user
552
+ */
553
+ list: (options?: {
554
+ read?: boolean;
555
+ type?: string;
556
+ limit?: number;
557
+ cursor?: string;
558
+ }) => Promise<ListNotificationsResponse>;
559
+ /**
560
+ * Mark specific notifications as read
561
+ */
562
+ markRead: (ids: string[]) => Promise<MarkNotificationsReadResponse>;
563
+ /**
564
+ * Mark all notifications as read
565
+ */
566
+ markAllRead: () => Promise<MarkAllNotificationsReadResponse>;
567
+ };
568
+ /**
569
+ * AI Services
570
+ */
571
+ ai: {
572
+ /**
573
+ * Natural language query — converts natural language to structured query
574
+ */
575
+ nlq: (request: AiNlqRequest) => Promise<AiNlqResponse>;
576
+ /**
577
+ * Multi-turn AI chat
578
+ */
579
+ chat: (request: AiChatRequest) => Promise<AiChatResponse>;
580
+ /**
581
+ * AI-powered field value suggestions
582
+ */
583
+ suggest: (request: AiSuggestRequest) => Promise<AiSuggestResponse>;
584
+ /**
585
+ * AI-powered data insights
586
+ */
587
+ insights: (request: AiInsightsRequest) => Promise<AiInsightsResponse>;
588
+ };
589
+ /**
590
+ * Internationalization Services
591
+ */
592
+ i18n: {
593
+ /**
594
+ * Get available locales
595
+ */
596
+ getLocales: () => Promise<GetLocalesResponse>;
597
+ /**
598
+ * Get translations for a locale
599
+ */
600
+ getTranslations: (locale: string, options?: {
601
+ namespace?: string;
602
+ keys?: string[];
603
+ }) => Promise<GetTranslationsResponse>;
604
+ /**
605
+ * Get translated field labels for an object
606
+ */
607
+ getFieldLabels: (object: string, locale: string) => Promise<GetFieldLabelsResponse>;
608
+ };
391
609
  /**
392
610
  * Data Operations
393
611
  */