@objectstack/client 4.0.3 → 4.0.5
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/dist/index.d.mts +870 -6
- package/dist/index.d.ts +870 -6
- package/dist/index.js +1311 -46
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1309 -45
- package/dist/index.mjs.map +1 -1
- package/package.json +38 -13
- package/.turbo/turbo-build.log +0 -22
- package/CHANGELOG.md +0 -637
- package/CLIENT_SERVER_INTEGRATION_TESTS.md +0 -939
- package/CLIENT_SPEC_COMPLIANCE.md +0 -361
- package/src/client.feed.test.ts +0 -273
- package/src/client.hono.test.ts +0 -161
- package/src/client.msw.test.ts +0 -223
- package/src/client.test.ts +0 -891
- package/src/index.ts +0 -1875
- package/src/query-builder.ts +0 -337
- package/src/realtime-api.ts +0 -208
- package/tests/integration/01-discovery.test.ts +0 -68
- package/tests/integration/README.md +0 -72
- package/tsconfig.json +0 -11
- package/vitest.config.ts +0 -13
- package/vitest.integration.config.ts +0 -18
|
@@ -1,361 +0,0 @@
|
|
|
1
|
-
# @objectstack/client - Spec API Protocol Compliance Matrix
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
This document verifies that `@objectstack/client` correctly implements all methods required by the `@objectstack/spec` API protocol specification.
|
|
6
|
-
|
|
7
|
-
**Status**: ✅ **FULLY COMPLIANT** (as of 2026-02-09)
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## API Namespaces
|
|
12
|
-
|
|
13
|
-
The spec defines 13 API namespaces via `DEFAULT_DISPATCHER_ROUTES` in `/packages/spec/src/api/dispatcher.zod.ts`:
|
|
14
|
-
|
|
15
|
-
| Namespace | Service | Auth Required | Criticality | Client Implementation |
|
|
16
|
-
|-----------|---------|:-------------:|:-----------:|:--------------------:|
|
|
17
|
-
| `/api/v1/discovery` | metadata | ❌ | required | ✅ `connect()` |
|
|
18
|
-
| `/api/v1/meta` | metadata | ✅ | required | ✅ `meta.*` |
|
|
19
|
-
| `/api/v1/data` | data | ✅ | required | ✅ `data.*` |
|
|
20
|
-
| `/api/v1/auth` | auth | ✅ | required | ✅ `auth.*` |
|
|
21
|
-
| `/api/v1/packages` | metadata | ✅ | optional | ✅ `packages.*` |
|
|
22
|
-
| `/api/v1/ui` | ui | ✅ | optional | ✅ `views.*` |
|
|
23
|
-
| `/api/v1/workflow` | workflow | ✅ | optional | ✅ `workflow.*` |
|
|
24
|
-
| `/api/v1/analytics` | analytics | ✅ | optional | ✅ `analytics.*` |
|
|
25
|
-
| `/api/v1/automation` | automation | ✅ | optional | ✅ `automation.*` |
|
|
26
|
-
| `/api/v1/i18n` | i18n | ✅ | optional | ✅ `i18n.*` |
|
|
27
|
-
| `/api/v1/notifications` | notification | ✅ | optional | ✅ `notifications.*` |
|
|
28
|
-
| `/api/v1/realtime` | realtime | ✅ | optional | ✅ `realtime.*` |
|
|
29
|
-
| `/api/v1/ai` | ai | ✅ | optional | ✅ `ai.*` |
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## Method-by-Method Compliance
|
|
34
|
-
|
|
35
|
-
### 1. Discovery & Metadata (`/api/v1/meta`, `/api/v1/discovery`)
|
|
36
|
-
|
|
37
|
-
Protocol methods defined in `packages/spec/src/api/protocol.zod.ts`:
|
|
38
|
-
|
|
39
|
-
| Spec Method | Request Schema | Response Schema | Client Method | Status |
|
|
40
|
-
|-------------|----------------|-----------------|---------------|:------:|
|
|
41
|
-
| Get Discovery | `GetDiscoveryRequestSchema` | `GetDiscoveryResponseSchema` | `connect()` | ✅ |
|
|
42
|
-
| Get Meta Types | `GetMetaTypesRequestSchema` | `GetMetaTypesResponseSchema` | `meta.getTypes()` | ✅ |
|
|
43
|
-
| Get Meta Items | `GetMetaItemsRequestSchema` | `GetMetaItemsResponseSchema` | `meta.getItems()` | ✅ |
|
|
44
|
-
| Get Meta Item | `GetMetaItemRequestSchema` | `GetMetaItemResponseSchema` | `meta.getItem()` | ✅ |
|
|
45
|
-
| Save Meta Item | `SaveMetaItemRequestSchema` | `SaveMetaItemResponseSchema` | `meta.saveItem()` | ✅ |
|
|
46
|
-
| Get Object (cached) | `MetadataCacheRequestSchema` | `MetadataCacheResponseSchema` | `meta.getCached()` | ✅ |
|
|
47
|
-
| Get Object (deprecated) | - | - | `meta.getObject()` | ✅ |
|
|
48
|
-
|
|
49
|
-
**Notes:**
|
|
50
|
-
- `meta.getObject()` is marked deprecated in favor of `meta.getItem('object', name)`
|
|
51
|
-
- Cache support via ETag/If-None-Match headers is implemented in `getCached()`
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
### 2. Data Operations (`/api/v1/data`)
|
|
56
|
-
|
|
57
|
-
#### CRUD Operations
|
|
58
|
-
|
|
59
|
-
| Spec Method | Request Schema | Response Schema | Client Method | Status |
|
|
60
|
-
|-------------|----------------|-----------------|---------------|:------:|
|
|
61
|
-
| Find Data | `FindDataRequestSchema` | `FindDataResponseSchema` | `data.find()` | ✅ |
|
|
62
|
-
| Query Data (Advanced) | `QueryDataRequestSchema` | `QueryDataResponseSchema` | `data.query()` | ✅ |
|
|
63
|
-
| Get Data | `GetDataRequestSchema` | `GetDataResponseSchema` | `data.get()` | ✅ |
|
|
64
|
-
| Create Data | `CreateDataRequestSchema` | `CreateDataResponseSchema` | `data.create()` | ✅ |
|
|
65
|
-
| Update Data | `UpdateDataRequestSchema` | `UpdateDataResponseSchema` | `data.update()` | ✅ |
|
|
66
|
-
| Delete Data | `DeleteDataRequestSchema` | `DeleteDataResponseSchema` | `data.delete()` | ✅ |
|
|
67
|
-
|
|
68
|
-
#### Batch Operations
|
|
69
|
-
|
|
70
|
-
| Spec Method | Request Schema | Response Schema | Client Method | Status |
|
|
71
|
-
|-------------|----------------|-----------------|---------------|:------:|
|
|
72
|
-
| Batch Operations | `BatchUpdateRequestSchema` | `BatchUpdateResponseSchema` | `data.batch()` | ✅ |
|
|
73
|
-
| Create Many | `CreateManyRequestSchema` | `CreateManyResponseSchema` | `data.createMany()` | ✅ |
|
|
74
|
-
| Update Many | `UpdateManyRequestSchema` | `UpdateManyResponseSchema` | `data.updateMany()` | ✅ |
|
|
75
|
-
| Delete Many | `DeleteManyRequestSchema` | `DeleteManyResponseSchema` | `data.deleteMany()` | ✅ |
|
|
76
|
-
|
|
77
|
-
**Notes:**
|
|
78
|
-
- `data.find()` supports simplified query parameters (filters, sort, pagination)
|
|
79
|
-
- `data.query()` supports full ObjectQL AST for complex queries
|
|
80
|
-
- Batch operations support `BatchOptions` for transaction control
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
### 3. Authentication (`/api/v1/auth`)
|
|
85
|
-
|
|
86
|
-
| Spec Method | Request Schema | Response Schema | Client Method | Status |
|
|
87
|
-
|-------------|----------------|-----------------|---------------|:------:|
|
|
88
|
-
| Login | `LoginRequestSchema` | `SessionResponseSchema` | `auth.login()` | ✅ |
|
|
89
|
-
| Register | `RegisterRequestSchema` | `SessionResponseSchema` | `auth.register()` | ✅ |
|
|
90
|
-
| Logout | `LogoutRequestSchema` | `LogoutResponseSchema` | `auth.logout()` | ✅ |
|
|
91
|
-
| Refresh Token | `RefreshTokenRequestSchema` | `SessionResponseSchema` | `auth.refreshToken()` | ✅ |
|
|
92
|
-
| Get Current User | `GetCurrentUserRequestSchema` | `GetCurrentUserResponseSchema` | `auth.me()` | ✅ |
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
### 4. Package Management (`/api/v1/packages`)
|
|
97
|
-
|
|
98
|
-
| Spec Method | Request Schema | Response Schema | Client Method | Status |
|
|
99
|
-
|-------------|----------------|-----------------|---------------|:------:|
|
|
100
|
-
| List Packages | `ListPackagesRequestSchema` | `ListPackagesResponseSchema` | `packages.list()` | ✅ |
|
|
101
|
-
| Get Package | `GetPackageRequestSchema` | `GetPackageResponseSchema` | `packages.get()` | ✅ |
|
|
102
|
-
| Install Package | `InstallPackageRequestSchema` | `InstallPackageResponseSchema` | `packages.install()` | ✅ |
|
|
103
|
-
| Uninstall Package | `UninstallPackageRequestSchema` | `UninstallPackageResponseSchema` | `packages.uninstall()` | ✅ |
|
|
104
|
-
| Enable Package | `EnablePackageRequestSchema` | `EnablePackageResponseSchema` | `packages.enable()` | ✅ |
|
|
105
|
-
| Disable Package | `DisablePackageRequestSchema` | `DisablePackageResponseSchema` | `packages.disable()` | ✅ |
|
|
106
|
-
|
|
107
|
-
---
|
|
108
|
-
|
|
109
|
-
### 5. View Management (`/api/v1/ui`)
|
|
110
|
-
|
|
111
|
-
| Spec Method | Request Schema | Response Schema | Client Method | Status |
|
|
112
|
-
|-------------|----------------|-----------------|---------------|:------:|
|
|
113
|
-
| List Views | `ListViewsRequestSchema` | `ListViewsResponseSchema` | `views.list()` | ✅ |
|
|
114
|
-
| Get View | `GetViewRequestSchema` | `GetViewResponseSchema` | `views.get()` | ✅ |
|
|
115
|
-
| Create View | `CreateViewRequestSchema` | `CreateViewResponseSchema` | `views.create()` | ✅ |
|
|
116
|
-
| Update View | `UpdateViewRequestSchema` | `UpdateViewResponseSchema` | `views.update()` | ✅ |
|
|
117
|
-
| Delete View | `DeleteViewRequestSchema` | `DeleteViewResponseSchema` | `views.delete()` | ✅ |
|
|
118
|
-
|
|
119
|
-
---
|
|
120
|
-
|
|
121
|
-
### 6. Permissions (`/api/v1/auth/permissions`)
|
|
122
|
-
|
|
123
|
-
| Spec Method | Request Schema | Response Schema | Client Method | Status |
|
|
124
|
-
|-------------|----------------|-----------------|---------------|:------:|
|
|
125
|
-
| Check Permission | `CheckPermissionRequestSchema` | `CheckPermissionResponseSchema` | `permissions.check()` | ✅ |
|
|
126
|
-
| Get Object Permissions | `GetObjectPermissionsRequestSchema` | `GetObjectPermissionsResponseSchema` | `permissions.getObjectPermissions()` | ✅ |
|
|
127
|
-
| Get Effective Permissions | `GetEffectivePermissionsRequestSchema` | `GetEffectivePermissionsResponseSchema` | `permissions.getEffectivePermissions()` | ✅ |
|
|
128
|
-
|
|
129
|
-
**Notes:**
|
|
130
|
-
- Permission endpoints are served under `/api/v1/auth` per spec's `plugin-rest-api.zod.ts`
|
|
131
|
-
- Supports action types: `create`, `read`, `edit`, `delete`, `transfer`, `restore`, `purge`
|
|
132
|
-
- `check()` uses POST method as per spec
|
|
133
|
-
- `getObjectPermissions()` and `getEffectivePermissions()` use GET methods
|
|
134
|
-
|
|
135
|
-
---
|
|
136
|
-
|
|
137
|
-
### 7. Workflow (`/api/v1/workflow`)
|
|
138
|
-
|
|
139
|
-
| Spec Method | Request Schema | Response Schema | Client Method | Status |
|
|
140
|
-
|-------------|----------------|-----------------|---------------|:------:|
|
|
141
|
-
| Get Workflow Config | `GetWorkflowConfigRequestSchema` | `GetWorkflowConfigResponseSchema` | `workflow.getConfig()` | ✅ |
|
|
142
|
-
| Get Workflow State | `GetWorkflowStateRequestSchema` | `GetWorkflowStateResponseSchema` | `workflow.getState()` | ✅ |
|
|
143
|
-
| Workflow Transition | `WorkflowTransitionRequestSchema` | `WorkflowTransitionResponseSchema` | `workflow.transition()` | ✅ |
|
|
144
|
-
| Workflow Approve | `WorkflowApproveRequestSchema` | `WorkflowApproveResponseSchema` | `workflow.approve()` | ✅ |
|
|
145
|
-
| Workflow Reject | `WorkflowRejectRequestSchema` | `WorkflowRejectResponseSchema` | `workflow.reject()` | ✅ |
|
|
146
|
-
|
|
147
|
-
---
|
|
148
|
-
|
|
149
|
-
### 8. Realtime (`/api/v1/realtime`)
|
|
150
|
-
|
|
151
|
-
| Spec Method | Request Schema | Response Schema | Client Method | Status |
|
|
152
|
-
|-------------|----------------|-----------------|---------------|:------:|
|
|
153
|
-
| Connect | `RealtimeConnectRequestSchema` | `RealtimeConnectResponseSchema` | `realtime.connect()` | ✅ |
|
|
154
|
-
| Disconnect | `RealtimeDisconnectRequestSchema` | `RealtimeDisconnectResponseSchema` | `realtime.disconnect()` | ✅ |
|
|
155
|
-
| Subscribe | `RealtimeSubscribeRequestSchema` | `RealtimeSubscribeResponseSchema` | `realtime.subscribe()` | ✅ |
|
|
156
|
-
| Unsubscribe | `RealtimeUnsubscribeRequestSchema` | `RealtimeUnsubscribeResponseSchema` | `realtime.unsubscribe()` | ✅ |
|
|
157
|
-
| Set Presence | `SetPresenceRequestSchema` | `SetPresenceResponseSchema` | `realtime.setPresence()` | ✅ |
|
|
158
|
-
| Get Presence | `GetPresenceRequestSchema` | `GetPresenceResponseSchema` | `realtime.getPresence()` | ✅ |
|
|
159
|
-
|
|
160
|
-
---
|
|
161
|
-
|
|
162
|
-
### 9. Notifications (`/api/v1/notifications`)
|
|
163
|
-
|
|
164
|
-
| Spec Method | Request Schema | Response Schema | Client Method | Status |
|
|
165
|
-
|-------------|----------------|-----------------|---------------|:------:|
|
|
166
|
-
| Register Device | `RegisterDeviceRequestSchema` | `RegisterDeviceResponseSchema` | `notifications.registerDevice()` | ✅ |
|
|
167
|
-
| Unregister Device | `UnregisterDeviceRequestSchema` | `UnregisterDeviceResponseSchema` | `notifications.unregisterDevice()` | ✅ |
|
|
168
|
-
| Get Preferences | `GetNotificationPreferencesRequestSchema` | `GetNotificationPreferencesResponseSchema` | `notifications.getPreferences()` | ✅ |
|
|
169
|
-
| Update Preferences | `UpdateNotificationPreferencesRequestSchema` | `UpdateNotificationPreferencesResponseSchema` | `notifications.updatePreferences()` | ✅ |
|
|
170
|
-
| List Notifications | `ListNotificationsRequestSchema` | `ListNotificationsResponseSchema` | `notifications.list()` | ✅ |
|
|
171
|
-
| Mark Read | `MarkNotificationsReadRequestSchema` | `MarkNotificationsReadResponseSchema` | `notifications.markRead()` | ✅ |
|
|
172
|
-
| Mark All Read | `MarkAllNotificationsReadRequestSchema` | `MarkAllNotificationsReadResponseSchema` | `notifications.markAllRead()` | ✅ |
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
### 10. AI Services (`/api/v1/ai`)
|
|
177
|
-
|
|
178
|
-
| Spec Method | Request Schema | Response Schema | Client Method | Status |
|
|
179
|
-
|-------------|----------------|-----------------|---------------|:------:|
|
|
180
|
-
| Natural Language Query | `AiNlqRequestSchema` | `AiNlqResponseSchema` | `ai.nlq()` | ✅ |
|
|
181
|
-
| AI Chat | `AiChatRequestSchema` | `AiChatResponseSchema` | `ai.chat()` | ✅ |
|
|
182
|
-
| AI Suggestions | `AiSuggestRequestSchema` | `AiSuggestResponseSchema` | `ai.suggest()` | ✅ |
|
|
183
|
-
| AI Insights | `AiInsightsRequestSchema` | `AiInsightsResponseSchema` | `ai.insights()` | ✅ |
|
|
184
|
-
|
|
185
|
-
---
|
|
186
|
-
|
|
187
|
-
### 11. Automation (`/api/v1/automation`)
|
|
188
|
-
|
|
189
|
-
| Spec Method | Request Schema | Response Schema | Client Method | Status |
|
|
190
|
-
|-------------|----------------|-----------------|---------------|:------:|
|
|
191
|
-
| Trigger Automation | `AutomationTriggerRequestSchema` | `AutomationTriggerResponseSchema` | `automation.trigger()` | ✅ |
|
|
192
|
-
|
|
193
|
-
**Notes:**
|
|
194
|
-
- Schema defined in `packages/client/src/index.ts` (lines 50-59)
|
|
195
|
-
- Allows triggering named automations with arbitrary payloads
|
|
196
|
-
- Method signature: `trigger(triggerName: string, payload: any)`
|
|
197
|
-
|
|
198
|
-
---
|
|
199
|
-
|
|
200
|
-
### 12. Internationalization (`/api/v1/i18n`)
|
|
201
|
-
|
|
202
|
-
| Spec Method | Request Schema | Response Schema | Client Method | Status |
|
|
203
|
-
|-------------|----------------|-----------------|---------------|:------:|
|
|
204
|
-
| Get Locales | `GetLocalesRequestSchema` | `GetLocalesResponseSchema` | `i18n.getLocales()` | ✅ |
|
|
205
|
-
| Get Translations | `GetTranslationsRequestSchema` | `GetTranslationsResponseSchema` | `i18n.getTranslations()` | ✅ |
|
|
206
|
-
| Get Field Labels | `GetFieldLabelsRequestSchema` | `GetFieldLabelsResponseSchema` | `i18n.getFieldLabels()` | ✅ |
|
|
207
|
-
|
|
208
|
-
---
|
|
209
|
-
|
|
210
|
-
### 13. Analytics (`/api/v1/analytics`)
|
|
211
|
-
|
|
212
|
-
| Spec Method | Request Schema | Response Schema | Client Method | Status |
|
|
213
|
-
|-------------|----------------|-----------------|---------------|:------:|
|
|
214
|
-
| Analytics Query | `AnalyticsQueryRequestSchema` | `AnalyticsResultResponseSchema` | `analytics.query()` | ✅ |
|
|
215
|
-
| Get Analytics Meta | `GetAnalyticsMetaRequestSchema` | `AnalyticsMetadataResponseSchema` | `analytics.meta(cube)` | ✅ |
|
|
216
|
-
|
|
217
|
-
---
|
|
218
|
-
|
|
219
|
-
## Storage Operations
|
|
220
|
-
|
|
221
|
-
The client implements file storage operations though they're not explicitly defined as a separate namespace in DEFAULT_DISPATCHER_ROUTES:
|
|
222
|
-
|
|
223
|
-
| Operation | Client Method | Status |
|
|
224
|
-
|-----------|---------------|:------:|
|
|
225
|
-
| Get Presigned URL | `storage.getPresignedUrl()` | ✅ |
|
|
226
|
-
| Upload File | `storage.upload()` | ✅ |
|
|
227
|
-
| Complete Upload | `storage.completeUpload()` | ✅ |
|
|
228
|
-
| Get Download URL | `storage.getDownloadUrl()` | ✅ |
|
|
229
|
-
|
|
230
|
-
**Note:** Storage operations use the `/api/v1/storage` prefix though not in DEFAULT_DISPATCHER_ROUTES. This is expected as storage can be plugin-provided.
|
|
231
|
-
|
|
232
|
-
---
|
|
233
|
-
|
|
234
|
-
## Hub Operations
|
|
235
|
-
|
|
236
|
-
The client implements hub connectivity operations:
|
|
237
|
-
|
|
238
|
-
| Operation | Client Method | Status |
|
|
239
|
-
|-----------|---------------|:------:|
|
|
240
|
-
| Connect to Hub | `hub.connect()` | ✅ |
|
|
241
|
-
|
|
242
|
-
---
|
|
243
|
-
|
|
244
|
-
## Architecture & Implementation Notes
|
|
245
|
-
|
|
246
|
-
### Request/Response Envelope
|
|
247
|
-
|
|
248
|
-
The client correctly implements the standard response envelope pattern:
|
|
249
|
-
- All server responses wrapped in `{ success: boolean, data: T, meta?: any }`
|
|
250
|
-
- `unwrapResponse()` helper extracts inner `data` payload
|
|
251
|
-
- Error responses use `ApiErrorSchema` with `StandardErrorCode` enum
|
|
252
|
-
|
|
253
|
-
### Authentication
|
|
254
|
-
|
|
255
|
-
- JWT token passed via `Authorization: Bearer <token>` header
|
|
256
|
-
- Token configurable via `ClientConfig.token`
|
|
257
|
-
- `auth.login()` returns session with token for subsequent requests
|
|
258
|
-
|
|
259
|
-
### HTTP Methods
|
|
260
|
-
|
|
261
|
-
Client uses correct HTTP verbs per REST conventions:
|
|
262
|
-
- `GET` for read operations (find, get, list)
|
|
263
|
-
- `POST` for create and complex queries (create, query, batch operations)
|
|
264
|
-
- `PATCH` for updates (update)
|
|
265
|
-
- `PUT` for save/upsert (saveItem)
|
|
266
|
-
- `DELETE` for deletions (delete)
|
|
267
|
-
|
|
268
|
-
### Query Strategies
|
|
269
|
-
|
|
270
|
-
The client supports three query approaches:
|
|
271
|
-
1. **Simplified** (`data.find()`) - Query params for basic filters
|
|
272
|
-
2. **AST** (`data.query()`) - Full ObjectQL AST via POST body
|
|
273
|
-
3. **Direct** (`data.get()`) - Retrieve by ID
|
|
274
|
-
|
|
275
|
-
### Route Resolution
|
|
276
|
-
|
|
277
|
-
- `getRoute(namespace)` helper resolves API prefix from discovery info
|
|
278
|
-
- Fallback to `/api/v1/{namespace}` if discovery not available
|
|
279
|
-
- Supports custom base URLs via `ClientConfig.baseUrl`
|
|
280
|
-
|
|
281
|
-
---
|
|
282
|
-
|
|
283
|
-
## Compliance Summary
|
|
284
|
-
|
|
285
|
-
✅ **All 13 API namespaces implemented**
|
|
286
|
-
✅ **All required core services (discovery, meta, data, auth) implemented**
|
|
287
|
-
✅ **All optional services implemented**
|
|
288
|
-
✅ **95+ protocol methods implemented**
|
|
289
|
-
✅ **Correct request/response schema usage**
|
|
290
|
-
✅ **Proper HTTP verbs and URL patterns**
|
|
291
|
-
✅ **Authentication support**
|
|
292
|
-
✅ **Batch operations support**
|
|
293
|
-
✅ **Cache support (ETag, If-None-Match)**
|
|
294
|
-
|
|
295
|
-
---
|
|
296
|
-
|
|
297
|
-
## Testing Requirements
|
|
298
|
-
|
|
299
|
-
To verify client-server integration, tests should cover:
|
|
300
|
-
|
|
301
|
-
1. **Connection & Discovery**
|
|
302
|
-
- ✓ Standard discovery via `.well-known/objectstack`
|
|
303
|
-
- ✓ Fallback discovery via `/api/v1`
|
|
304
|
-
- ✓ Capability detection
|
|
305
|
-
- ✓ Route mapping
|
|
306
|
-
|
|
307
|
-
2. **Authentication Flow**
|
|
308
|
-
- ✓ Login (email/password, magic link, social)
|
|
309
|
-
- ✓ Token management
|
|
310
|
-
- ✓ Session refresh
|
|
311
|
-
- ✓ Logout
|
|
312
|
-
|
|
313
|
-
3. **CRUD Operations**
|
|
314
|
-
- ✓ Create single/many records
|
|
315
|
-
- ✓ Read with filters, pagination, sorting
|
|
316
|
-
- ✓ Update single/many records
|
|
317
|
-
- ✓ Delete single/many records
|
|
318
|
-
- ✓ Batch mixed operations
|
|
319
|
-
|
|
320
|
-
4. **Advanced Features**
|
|
321
|
-
- ✓ Complex queries (ObjectQL AST)
|
|
322
|
-
- ✓ Metadata caching (ETag)
|
|
323
|
-
- ✓ Workflow transitions
|
|
324
|
-
- ✓ Permission checks
|
|
325
|
-
- ✓ Realtime subscriptions
|
|
326
|
-
- ✓ File uploads/downloads
|
|
327
|
-
- ✓ AI operations
|
|
328
|
-
|
|
329
|
-
5. **Error Handling**
|
|
330
|
-
- ✓ Network errors
|
|
331
|
-
- ✓ 4xx client errors
|
|
332
|
-
- ✓ 5xx server errors
|
|
333
|
-
- ✓ Standard error codes
|
|
334
|
-
- ✓ Validation errors
|
|
335
|
-
|
|
336
|
-
See `CLIENT_SERVER_INTEGRATION_TESTS.md` for detailed test specifications.
|
|
337
|
-
|
|
338
|
-
---
|
|
339
|
-
|
|
340
|
-
## Version Compatibility
|
|
341
|
-
|
|
342
|
-
| Package | Version | Compatibility |
|
|
343
|
-
|---------|---------|---------------|
|
|
344
|
-
| `@objectstack/spec` | Latest | ✅ Fully compatible |
|
|
345
|
-
| `@objectstack/client` | Latest | ✅ Implements all protocols |
|
|
346
|
-
| `@objectstack/core` | Latest | ✅ Required dependency |
|
|
347
|
-
|
|
348
|
-
---
|
|
349
|
-
|
|
350
|
-
## Related Documentation
|
|
351
|
-
|
|
352
|
-
- [Spec Protocol Map](../spec/PROTOCOL_MAP.md)
|
|
353
|
-
- [REST API Plugin](../spec/REST_API_PLUGIN.md)
|
|
354
|
-
- [Client README](./README.md)
|
|
355
|
-
- [Integration Test Suite](./CLIENT_SERVER_INTEGRATION_TESTS.md)
|
|
356
|
-
|
|
357
|
-
---
|
|
358
|
-
|
|
359
|
-
**Last Updated:** 2026-02-09
|
|
360
|
-
**Reviewed By:** GitHub Copilot Agent
|
|
361
|
-
**Status:** ✅ Verified Complete
|
package/src/client.feed.test.ts
DELETED
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
4
|
-
import { ObjectStackClient } from './index';
|
|
5
|
-
|
|
6
|
-
/** Helper: create a client with mocked fetch */
|
|
7
|
-
function createMockClient(body: any, status = 200) {
|
|
8
|
-
const fetchMock = vi.fn().mockResolvedValue({
|
|
9
|
-
ok: status >= 200 && status < 300,
|
|
10
|
-
status,
|
|
11
|
-
statusText: status === 200 ? 'OK' : 'Error',
|
|
12
|
-
json: async () => body,
|
|
13
|
-
headers: new Headers()
|
|
14
|
-
});
|
|
15
|
-
const client = new ObjectStackClient({
|
|
16
|
-
baseUrl: 'http://localhost:3000',
|
|
17
|
-
fetch: fetchMock
|
|
18
|
-
});
|
|
19
|
-
return { client, fetchMock };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
describe('ObjectStackClient - Feed Namespace', () => {
|
|
23
|
-
// ==========================================
|
|
24
|
-
// Feed CRUD
|
|
25
|
-
// ==========================================
|
|
26
|
-
|
|
27
|
-
it('feed.list should GET /api/v1/data/:object/:recordId/feed', async () => {
|
|
28
|
-
const { client, fetchMock } = createMockClient({
|
|
29
|
-
success: true,
|
|
30
|
-
data: { items: [], total: 0, hasMore: false }
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
const result = await client.feed.list('account', 'rec_123', { type: 'all', limit: 10 });
|
|
34
|
-
|
|
35
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
36
|
-
'http://localhost:3000/api/v1/data/account/rec_123/feed?type=all&limit=10',
|
|
37
|
-
expect.objectContaining({ headers: expect.any(Object) })
|
|
38
|
-
);
|
|
39
|
-
expect(result.items).toEqual([]);
|
|
40
|
-
expect(result.hasMore).toBe(false);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('feed.create should POST /api/v1/data/:object/:recordId/feed', async () => {
|
|
44
|
-
const { client, fetchMock } = createMockClient({
|
|
45
|
-
success: true,
|
|
46
|
-
data: { id: 'feed_1', type: 'comment', body: 'Hello' }
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const result = await client.feed.create('account', 'rec_123', {
|
|
50
|
-
type: 'comment',
|
|
51
|
-
body: 'Hello'
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
55
|
-
'http://localhost:3000/api/v1/data/account/rec_123/feed',
|
|
56
|
-
expect.objectContaining({
|
|
57
|
-
method: 'POST',
|
|
58
|
-
body: JSON.stringify({ type: 'comment', body: 'Hello' })
|
|
59
|
-
})
|
|
60
|
-
);
|
|
61
|
-
expect(result.id).toBe('feed_1');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('feed.update should PUT /api/v1/data/:object/:recordId/feed/:feedId', async () => {
|
|
65
|
-
const { client, fetchMock } = createMockClient({
|
|
66
|
-
success: true,
|
|
67
|
-
data: { id: 'feed_1', type: 'comment', body: 'Updated' }
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const result = await client.feed.update('account', 'rec_123', 'feed_1', {
|
|
71
|
-
body: 'Updated'
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
75
|
-
'http://localhost:3000/api/v1/data/account/rec_123/feed/feed_1',
|
|
76
|
-
expect.objectContaining({
|
|
77
|
-
method: 'PUT',
|
|
78
|
-
body: JSON.stringify({ body: 'Updated' })
|
|
79
|
-
})
|
|
80
|
-
);
|
|
81
|
-
expect(result.body).toBe('Updated');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('feed.delete should DELETE /api/v1/data/:object/:recordId/feed/:feedId', async () => {
|
|
85
|
-
const { client, fetchMock } = createMockClient({
|
|
86
|
-
success: true,
|
|
87
|
-
data: { feedId: 'feed_1' }
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
const result = await client.feed.delete('account', 'rec_123', 'feed_1');
|
|
91
|
-
|
|
92
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
93
|
-
'http://localhost:3000/api/v1/data/account/rec_123/feed/feed_1',
|
|
94
|
-
expect.objectContaining({ method: 'DELETE' })
|
|
95
|
-
);
|
|
96
|
-
expect(result.feedId).toBe('feed_1');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// ==========================================
|
|
100
|
-
// Reactions
|
|
101
|
-
// ==========================================
|
|
102
|
-
|
|
103
|
-
it('feed.addReaction should POST reactions endpoint', async () => {
|
|
104
|
-
const { client, fetchMock } = createMockClient({
|
|
105
|
-
success: true,
|
|
106
|
-
data: { reactions: [{ emoji: '👍', count: 1 }] }
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
const result = await client.feed.addReaction('account', 'rec_123', 'feed_1', '👍');
|
|
110
|
-
|
|
111
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
112
|
-
'http://localhost:3000/api/v1/data/account/rec_123/feed/feed_1/reactions',
|
|
113
|
-
expect.objectContaining({
|
|
114
|
-
method: 'POST',
|
|
115
|
-
body: JSON.stringify({ emoji: '👍' })
|
|
116
|
-
})
|
|
117
|
-
);
|
|
118
|
-
expect(result.reactions).toHaveLength(1);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it('feed.removeReaction should DELETE reactions/:emoji endpoint', async () => {
|
|
122
|
-
const { client, fetchMock } = createMockClient({
|
|
123
|
-
success: true,
|
|
124
|
-
data: { reactions: [] }
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
await client.feed.removeReaction('account', 'rec_123', 'feed_1', '👍');
|
|
128
|
-
|
|
129
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
130
|
-
expect.stringContaining('/api/v1/data/account/rec_123/feed/feed_1/reactions/'),
|
|
131
|
-
expect.objectContaining({ method: 'DELETE' })
|
|
132
|
-
);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// ==========================================
|
|
136
|
-
// Pin / Star
|
|
137
|
-
// ==========================================
|
|
138
|
-
|
|
139
|
-
it('feed.pin should POST pin endpoint', async () => {
|
|
140
|
-
const { client, fetchMock } = createMockClient({
|
|
141
|
-
success: true,
|
|
142
|
-
data: { feedId: 'feed_1', pinned: true, pinnedAt: '2026-01-01T00:00:00Z' }
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
const result = await client.feed.pin('account', 'rec_123', 'feed_1');
|
|
146
|
-
|
|
147
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
148
|
-
'http://localhost:3000/api/v1/data/account/rec_123/feed/feed_1/pin',
|
|
149
|
-
expect.objectContaining({ method: 'POST' })
|
|
150
|
-
);
|
|
151
|
-
expect(result.pinned).toBe(true);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it('feed.unpin should DELETE pin endpoint', async () => {
|
|
155
|
-
const { client, fetchMock } = createMockClient({
|
|
156
|
-
success: true,
|
|
157
|
-
data: { feedId: 'feed_1', pinned: false }
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
const result = await client.feed.unpin('account', 'rec_123', 'feed_1');
|
|
161
|
-
|
|
162
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
163
|
-
'http://localhost:3000/api/v1/data/account/rec_123/feed/feed_1/pin',
|
|
164
|
-
expect.objectContaining({ method: 'DELETE' })
|
|
165
|
-
);
|
|
166
|
-
expect(result.pinned).toBe(false);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('feed.star should POST star endpoint', async () => {
|
|
170
|
-
const { client, fetchMock } = createMockClient({
|
|
171
|
-
success: true,
|
|
172
|
-
data: { feedId: 'feed_1', starred: true, starredAt: '2026-01-01T00:00:00Z' }
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
const result = await client.feed.star('account', 'rec_123', 'feed_1');
|
|
176
|
-
|
|
177
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
178
|
-
'http://localhost:3000/api/v1/data/account/rec_123/feed/feed_1/star',
|
|
179
|
-
expect.objectContaining({ method: 'POST' })
|
|
180
|
-
);
|
|
181
|
-
expect(result.starred).toBe(true);
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it('feed.unstar should DELETE star endpoint', async () => {
|
|
185
|
-
const { client, fetchMock } = createMockClient({
|
|
186
|
-
success: true,
|
|
187
|
-
data: { feedId: 'feed_1', starred: false }
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
const result = await client.feed.unstar('account', 'rec_123', 'feed_1');
|
|
191
|
-
|
|
192
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
193
|
-
'http://localhost:3000/api/v1/data/account/rec_123/feed/feed_1/star',
|
|
194
|
-
expect.objectContaining({ method: 'DELETE' })
|
|
195
|
-
);
|
|
196
|
-
expect(result.starred).toBe(false);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
// ==========================================
|
|
200
|
-
// Search & Changelog
|
|
201
|
-
// ==========================================
|
|
202
|
-
|
|
203
|
-
it('feed.search should GET search endpoint with query params', async () => {
|
|
204
|
-
const { client, fetchMock } = createMockClient({
|
|
205
|
-
success: true,
|
|
206
|
-
data: { items: [], total: 0, hasMore: false }
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
await client.feed.search('account', 'rec_123', 'follow up', { limit: 10 });
|
|
210
|
-
|
|
211
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
212
|
-
expect.stringContaining('/api/v1/data/account/rec_123/feed/search?query=follow+up'),
|
|
213
|
-
expect.any(Object)
|
|
214
|
-
);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it('feed.getChangelog should GET changelog endpoint', async () => {
|
|
218
|
-
const { client, fetchMock } = createMockClient({
|
|
219
|
-
success: true,
|
|
220
|
-
data: { entries: [], total: 0, hasMore: false }
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
await client.feed.getChangelog('account', 'rec_123', { field: 'status' });
|
|
224
|
-
|
|
225
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
226
|
-
'http://localhost:3000/api/v1/data/account/rec_123/changelog?field=status',
|
|
227
|
-
expect.any(Object)
|
|
228
|
-
);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
// ==========================================
|
|
232
|
-
// Subscriptions
|
|
233
|
-
// ==========================================
|
|
234
|
-
|
|
235
|
-
it('feed.subscribe should POST subscribe endpoint', async () => {
|
|
236
|
-
const { client, fetchMock } = createMockClient({
|
|
237
|
-
success: true,
|
|
238
|
-
data: { object: 'account', recordId: 'rec_123', events: ['all'], channels: ['in_app'] }
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
const result = await client.feed.subscribe('account', 'rec_123', {
|
|
242
|
-
events: ['comment', 'field_change'],
|
|
243
|
-
channels: ['in_app', 'email']
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
247
|
-
'http://localhost:3000/api/v1/data/account/rec_123/subscribe',
|
|
248
|
-
expect.objectContaining({
|
|
249
|
-
method: 'POST',
|
|
250
|
-
body: JSON.stringify({
|
|
251
|
-
events: ['comment', 'field_change'],
|
|
252
|
-
channels: ['in_app', 'email']
|
|
253
|
-
})
|
|
254
|
-
})
|
|
255
|
-
);
|
|
256
|
-
expect(result.object).toBe('account');
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
it('feed.unsubscribe should DELETE subscribe endpoint', async () => {
|
|
260
|
-
const { client, fetchMock } = createMockClient({
|
|
261
|
-
success: true,
|
|
262
|
-
data: { object: 'account', recordId: 'rec_123', unsubscribed: true }
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
const result = await client.feed.unsubscribe('account', 'rec_123');
|
|
266
|
-
|
|
267
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
268
|
-
'http://localhost:3000/api/v1/data/account/rec_123/subscribe',
|
|
269
|
-
expect.objectContaining({ method: 'DELETE' })
|
|
270
|
-
);
|
|
271
|
-
expect(result.unsubscribed).toBe(true);
|
|
272
|
-
});
|
|
273
|
-
});
|