Skip to content

Commit 7ffc3d3

Browse files
os-zhuangclaude
andauthored
feat(client,spec)!: delete 21 dead SDK methods + the four ghost route tables under them (#3612) (#3616)
Five client families built URLs no server surface mounts (dispatcher, REST, and autonomous service mounts all checked in the #3587 audit): permissions x3, realtime x6, workflow x3, views x5, notifications device/prefs x4. Every call was a guaranteed 404. Each family was underwritten only by an unconsumed spec DEFAULT_*_ROUTES table — the same disease DEFAULT_DISPATCHER_ROUTES had (#3586) — so DEFAULT_PERMISSION/VIEW/ WORKFLOW/REALTIME_ROUTES go with them; getDefaultRouteRegistrations() returns 9. ApiRouteType loses its client-only 'views'|'permissions' extras. Kept: events (local buffer, no HTTP), notifications list/markRead/ markAllRead (dispatcher-served), approvals.* (ADR-0019), and meta.getLegalNextStates (the real FSM read). Docs/README/integration-test spec swept; api-surface.json regenerated (-4 exports). Re-adding any of these surfaces now requires the server route to exist and a route-ledger row proving it (#3569/#3609 guards). The one external consumer (objectui useClientNotifications dead delegates) is trimmed in a companion objectui change. Closes #3612 Claude-Session: https://claude.ai/code/session_01LX9ut3MK3KykE11S9bJmv5 Co-authored-by: Claude <noreply@anthropic.com>
1 parent f24cb83 commit 7ffc3d3

9 files changed

Lines changed: 114 additions & 1178 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
"@objectstack/client": minor
3+
"@objectstack/spec": minor
4+
---
5+
6+
feat(client,spec)!: delete the 21 dead SDK methods and the four ghost route
7+
tables that underwrote them (#3612, #3587 finding)
8+
9+
Five client surface families built URLs that exist on NO server surface —
10+
not the dispatcher, not `@objectstack/rest`, not the autonomous service
11+
mounts — so every call was a guaranteed 404:
12+
13+
- `permissions` (check, getObjectPermissions, getEffectivePermissions)
14+
- `realtime` (connect, disconnect, subscribe, unsubscribe, setPresence,
15+
getPresence) — `service-realtime` registers zero HTTP routes and the
16+
dispatcher deliberately never advertises `/realtime`
17+
- `workflow` (getConfig, getState, transition)
18+
- `views` CRUD (list, get, create, update, delete) — no `/ui/views` route
19+
anywhere
20+
- `notifications` device/preference helpers (registerDevice,
21+
unregisterDevice, getPreferences, updatePreferences) — the ADR-0012
22+
server side was never built
23+
24+
Each family was underwritten only by an unconsumed spec `DEFAULT_*_ROUTES`
25+
table — the same disease `DEFAULT_DISPATCHER_ROUTES` had (#3586) — so
26+
`DEFAULT_PERMISSION_ROUTES`, `DEFAULT_VIEW_ROUTES`, `DEFAULT_WORKFLOW_ROUTES`,
27+
and `DEFAULT_REALTIME_ROUTES` are deleted with them;
28+
`getDefaultRouteRegistrations()` now returns 9 registrations.
29+
`ApiRouteType` loses its client-only `'views' | 'permissions'` extras.
30+
31+
Kept: `client.events` (explicitly local in-memory buffer, no HTTP),
32+
`notifications.list/markRead/markAllRead` (dispatcher-served),
33+
`approvals.*` (ADR-0019 — the real approval decision API), and
34+
`meta.getLegalNextStates` (the real FSM read).
35+
36+
Breaking for anyone calling the removed methods — a repo-wide and
37+
objectui-wide sweep found one consumer (`useClientNotifications`'s dead
38+
device/preference delegates, trimmed in the objectui companion change);
39+
shipped as minor per the launch-window convention (cf. #3562/#3581/#3595).
40+
Re-adding any of these surfaces requires the server route to exist and a
41+
route-ledger row proving it (#3569/#3609 guards).

content/docs/api/client-sdk.mdx

Lines changed: 7 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,7 @@ The `@objectstack/client` SDK aims to implement the ObjectStack API protocol spe
112112
| **meta** || 11 | Metadata read/write, published versions & drafts (ADR-0033), FSM introspection (ADR-0020) |
113113
| **data** || 10 | CRUD & query operations |
114114
| **auth** || 5 | Authentication & user management |
115-
| **permissions** || 3 | Access control checks |
116115
| **packages** || 17 | Package lifecycle: install/enable, drafts (ADR-0033), commits & rollback (ADR-0067), export/duplicate (ADR-0070) |
117-
| **views** || 5 | UI view definitions |
118-
| **workflow** || 3 | Workflow state transitions |
119116
| **analytics** || 3 | Analytics queries |
120117
| **automation** || 18 | Flow CRUD, trigger/execute, runs, screen-flow resume, descriptor/status registries |
121118
| **actions** || 2 | Server-registered action handlers (`engine.registerAction`) |
@@ -124,10 +121,13 @@ The `@objectstack/client` SDK aims to implement the ObjectStack API protocol spe
124121
| **security** || 3 | Suggested audience bindings (admin) |
125122
| **storage** || 2 | File upload & download |
126123
| **i18n** || 3 | Internationalization |
127-
| **notifications** | 🟡 | 7 | Legacy notification helpers; receipt/inbox cut-over pending |
128-
| **realtime** || 6 | Connection/subscription/presence calls (server transport is plugin-provided) |
124+
| **notifications** || 3 | List, mark-read, mark-all-read (inbox/receipt spine, ADR-0030) |
129125
| **ai** || 3 | AI services (NLQ, suggest, insights) |
130126

127+
The former `permissions`, `views`, `workflow`, and `realtime` namespaces (and
128+
the notifications device/preference helpers) were removed in #3612: no server
129+
surface ever mounted their routes, so every call was a guaranteed 404.
130+
131131
<Callout type="info">
132132
**Coverage is CI-enforced, not hand-asserted**: the #3563 route ledger
133133
([`route-ledger.ts`](https://github.com/objectstack-ai/objectstack/blob/main/packages/runtime/src/route-ledger.ts))
@@ -275,16 +275,6 @@ await client.auth.me();
275275
await client.auth.logout();
276276
await client.auth.refreshToken('refresh-token-string');
277277

278-
// Permissions — Access control checks
279-
await client.permissions.check({ object: 'account', action: 'create' });
280-
await client.permissions.getObjectPermissions('account');
281-
await client.permissions.getEffectivePermissions();
282-
283-
// Workflow — State machine management
284-
await client.workflow.getConfig('approval');
285-
await client.workflow.getState('approval', recordId);
286-
await client.workflow.transition({ object: 'approval', recordId, transition: 'submit' });
287-
288278
// Approvals — request-based decision API (ADR-0019)
289279
// Approval is a flow node, not a workflow step: decisions are keyed by request id.
290280
await client.approvals.listRequests({ status: 'pending' }); // "my approvals" inbox
@@ -293,19 +283,7 @@ await client.approvals.approve(requestId, { comment: 'Looks good' });
293283
await client.approvals.reject(requestId, { comment: 'Incomplete' });
294284
await client.approvals.listActions(requestId); // audit trail
295285

296-
// Realtime — WebSocket subscriptions
297-
await client.realtime.connect({ transport: 'websocket' });
298-
await client.realtime.subscribe({ channel: 'account', events: ['record.updated'] });
299-
await client.realtime.unsubscribe('subscription-id');
300-
await client.realtime.setPresence('account', { userId: 'user-123', status: 'online', lastSeen: new Date().toISOString() });
301-
await client.realtime.getPresence('account');
302-
await client.realtime.disconnect();
303-
304-
// Notifications — legacy helper surface
305-
await client.notifications.registerDevice({ token: 'device-token', platform: 'ios' });
306-
await client.notifications.unregisterDevice('device-id');
307-
await client.notifications.getPreferences();
308-
await client.notifications.updatePreferences({ email: true, push: false });
286+
// Notifications — inbox/receipt spine (ADR-0030)
309287
await client.notifications.list({ read: false });
310288
await client.notifications.markRead(['notif-1', 'notif-2']);
311289
await client.notifications.markAllRead();
@@ -374,17 +352,10 @@ await client.security.suggestedBindings.confirm(suggestions[0].id);
374352
// Storage — File upload and management
375353
await client.storage.upload(fileData, 'user');
376354
await client.storage.getDownloadUrl('file-123');
377-
378-
// Views — UI view management
379-
await client.views.list('account');
380-
await client.views.get('account', viewId);
381-
await client.views.create('account', { name: 'my_view', ... });
382-
await client.views.update('account', viewId, { ... });
383-
await client.views.delete('account', viewId);
384355
```
385356

386357
<Callout type="info">
387-
**Service availability**: Optional services (workflow, ai, etc.) are only available when the corresponding plugin is installed on the server. Always check the `services` map on the discovery result returned by `client.connect()` (cache it yourself — the client has no `discovery` getter) to verify service availability before calling these methods.
358+
**Service availability**: Optional services (automation, ai, etc.) are only available when the corresponding plugin is installed on the server. Always check the `services` map on the discovery result returned by `client.connect()` (cache it yourself — the client has no `discovery` getter) to verify service availability before calling these methods.
388359
</Callout>
389360

390361
---

packages/client/CLIENT_SERVER_INTEGRATION_TESTS.md

Lines changed: 14 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ This document defines comprehensive integration tests for validating `@objectsta
1414
- ObjectStack server instance running
1515
- Test database (SQLite/Postgres) with sample data
1616
- All core services enabled (metadata, data, auth)
17-
- Optional services enabled (workflow, ai, realtime, etc.)
17+
- Optional services enabled (automation, ai, etc.)
1818

1919
2. **Client Configuration:**
2020
```typescript
@@ -43,17 +43,13 @@ packages/client/tests/integration/
4343
├── 04-data-crud.test.ts # Basic CRUD operations
4444
├── 05-data-batch.test.ts # Batch operations
4545
├── 06-data-query.test.ts # Advanced queries
46-
├── 07-permissions.test.ts # Permission checking
47-
├── 08-workflow.test.ts # Workflow operations
48-
├── 09-realtime.test.ts # Realtime subscriptions
49-
├── 10-notifications.test.ts # Notifications
50-
├── 11-ai.test.ts # AI services
51-
├── 12-i18n.test.ts # Internationalization
52-
├── 13-analytics.test.ts # Analytics queries
53-
├── 14-packages.test.ts # Package management
54-
├── 15-views.test.ts # View management
55-
├── 16-storage.test.ts # File storage
56-
├── 17-automation.test.ts # Automation triggers
46+
├── 07-notifications.test.ts # Notifications
47+
├── 08-ai.test.ts # AI services
48+
├── 09-i18n.test.ts # Internationalization
49+
├── 10-analytics.test.ts # Analytics queries
50+
├── 11-packages.test.ts # Package management
51+
├── 12-storage.test.ts # File storage
52+
├── 13-automation.test.ts # Automation triggers
5753
└── helpers/
5854
├── test-server.ts # Mock/stub server helpers
5955
├── test-data.ts # Test data generators
@@ -608,145 +604,15 @@ test('should execute aggregation query', async () => {
608604

609605
---
610606

611-
### 7. Permissions (`07-permissions.test.ts`)
607+
### 7-8. Permissions & Workflow — removed (#3612)
612608

613-
#### TC-PERM-001: Check Create Permission
614-
```typescript
615-
test('should check if user can create records', async () => {
616-
const client = await createAuthenticatedClient();
617-
618-
const result = await client.permissions.check({
619-
object: 'test_contact',
620-
action: 'create'
621-
});
622-
623-
expect(result.allowed).toBe(true);
624-
expect(result.deniedFields).toBeUndefined();
625-
});
626-
```
627-
628-
#### TC-PERM-002: Get Object Permissions
629-
```typescript
630-
test('should retrieve object-level permissions', async () => {
631-
const client = await createAuthenticatedClient();
632-
633-
const perms = await client.permissions.getObjectPermissions('test_contact');
634-
635-
expect(perms.object).toBe('test_contact');
636-
expect(perms.permissions).toBeDefined();
637-
expect(perms.fieldPermissions).toBeDefined();
638-
});
639-
```
640-
641-
#### TC-PERM-003: Get Effective Permissions
642-
```typescript
643-
test('should get effective permissions for current user', async () => {
644-
const client = await createAuthenticatedClient();
645-
646-
const effective = await client.permissions.getEffectivePermissions('test_contact');
647-
648-
expect(effective.canCreate).toBeDefined();
649-
expect(effective.canRead).toBeDefined();
650-
expect(effective.canEdit).toBeDefined();
651-
expect(effective.canDelete).toBeDefined();
652-
expect(effective.fields).toBeDefined();
653-
});
654-
```
655-
656-
---
657-
658-
### 8. Workflow (`08-workflow.test.ts`)
659-
660-
#### TC-WF-001: Get Workflow Configuration
661-
```typescript
662-
test('should retrieve workflow rules for object', async () => {
663-
const client = await createAuthenticatedClient();
664-
665-
const config = await client.workflow.getConfig('test_approval');
666-
667-
expect(config.object).toBe('test_approval');
668-
expect(config.states).toBeDefined();
669-
expect(config.transitions).toBeDefined();
670-
});
671-
```
672-
673-
#### TC-WF-002: Get Workflow State
674-
```typescript
675-
test('should get current workflow state and available transitions', async () => {
676-
const client = await createAuthenticatedClient();
677-
678-
const record = await client.data.create('test_approval', {
679-
title: 'Test Approval',
680-
status: 'draft'
681-
});
682-
683-
const state = await client.workflow.getState('test_approval', record.id);
684-
685-
expect(state.currentState).toBe('draft');
686-
expect(state.availableTransitions).toContain('submit');
687-
});
688-
```
689-
690-
#### TC-WF-003: Execute Workflow Transition
691-
```typescript
692-
test('should execute workflow state transition', async () => {
693-
const client = await createAuthenticatedClient();
694-
695-
const record = await client.data.create('test_approval', {
696-
title: 'Test',
697-
status: 'draft'
698-
});
699-
700-
const result = await client.workflow.transition({
701-
object: 'test_approval',
702-
recordId: record.id,
703-
transition: 'submit',
704-
comment: 'Submitting for approval'
705-
});
706-
707-
expect(result.success).toBe(true);
708-
expect(result.newState).toBe('pending');
709-
});
710-
```
711-
712-
#### TC-WF-004: Approve Workflow
713-
```typescript
714-
test('should approve workflow transition', async () => {
715-
const client = await createAuthenticatedClient();
716-
717-
const result = await client.workflow.approve({
718-
object: 'test_approval',
719-
recordId: testRecordId,
720-
comment: 'Approved by manager'
721-
});
722-
723-
expect(result.success).toBe(true);
724-
expect(result.newState).toBe('approved');
725-
});
726-
```
727-
728-
#### TC-WF-005: Reject Workflow
729-
```typescript
730-
test('should reject workflow transition', async () => {
731-
const client = await createAuthenticatedClient();
732-
733-
const result = await client.workflow.reject({
734-
object: 'test_approval',
735-
recordId: testRecordId,
736-
reason: 'Insufficient documentation',
737-
comment: 'Please provide more details'
738-
});
739-
740-
expect(result.success).toBe(true);
741-
expect(result.newState).toBe('rejected');
742-
});
743-
```
744-
745-
---
609+
The `permissions` and `workflow` client namespaces were deleted: no server
610+
surface ever mounted their routes. State-machine reads live on
611+
`meta.getLegalNextStates`; approval decisions are `client.approvals` (ADR-0019).
746612

747-
### 9-17. Additional Test Categories
613+
### 9-13. Additional Test Categories
748614

749-
*(Similar detailed test cases for remaining namespaces: Realtime, Notifications, AI, i18n, Analytics, Packages, Views, Storage, Automation)*
615+
*(Similar detailed test cases for remaining namespaces: Notifications, AI, i18n, Analytics, Packages, Storage, Automation)**
750616

751617
---
752618

0 commit comments

Comments
 (0)