@proteos/sdk 0.44.0 → 0.46.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/dist/{chunk-33EULWIM.cjs → chunk-F5DHWOGW.cjs} +195 -27
- package/dist/chunk-F5DHWOGW.cjs.map +1 -0
- package/dist/{chunk-IGIAJVX6.js → chunk-Y4CEHIRX.js} +191 -28
- package/dist/chunk-Y4CEHIRX.js.map +1 -0
- package/dist/index.cjs +453 -178
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +686 -13
- package/dist/index.d.ts +686 -13
- package/dist/index.js +355 -96
- package/dist/index.js.map +1 -1
- package/dist/meta/index.cjs +69 -53
- package/dist/meta/index.d.cts +1 -1
- package/dist/meta/index.d.ts +1 -1
- package/dist/meta/index.js +1 -1
- package/dist/{types-BTdBFq34.d.cts → types-D1PRIsSO.d.cts} +406 -14
- package/dist/{types-BTdBFq34.d.ts → types-D1PRIsSO.d.ts} +406 -14
- package/package.json +3 -3
- package/src/auth/index.ts +58 -1
- package/src/auth/me.ts +14 -1
- package/src/auth/platform-entities.ts +27 -0
- package/src/auth/roles.ts +3 -1
- package/src/auth/shares.ts +181 -0
- package/src/auth/teams.ts +135 -0
- package/src/auth/types.ts +192 -4
- package/src/auth/user-role-assignments.ts +65 -0
- package/src/conversation/index.ts +42 -0
- package/src/conversation/types.ts +37 -0
- package/src/index.ts +36 -0
- package/src/knowledge/graph.ts +6 -2
- package/src/knowledge/index.ts +15 -0
- package/src/knowledge/nodes.ts +4 -1
- package/src/knowledge/spaces.ts +86 -0
- package/src/knowledge/types.ts +89 -0
- package/src/meta/index.ts +11 -0
- package/src/meta/layout/control-registry.json +151 -26
- package/src/meta/types.ts +186 -4
- package/src/workflow/types.ts +20 -3
- package/dist/chunk-33EULWIM.cjs.map +0 -1
- package/dist/chunk-IGIAJVX6.js.map +0 -1
package/src/auth/index.ts
CHANGED
|
@@ -2,6 +2,12 @@ import type { ProteosClient } from '../client.js'
|
|
|
2
2
|
import { type MeService, MeServiceImpl } from './me.js'
|
|
3
3
|
import { type OrganizationService, OrganizationServiceImpl } from './organizations.js'
|
|
4
4
|
import { type RoleService, RoleServiceImpl } from './roles.js'
|
|
5
|
+
import { type ShareService, ShareServiceImpl } from './shares.js'
|
|
6
|
+
import { type TeamService, TeamServiceImpl } from './teams.js'
|
|
7
|
+
import {
|
|
8
|
+
type UserRoleAssignmentService,
|
|
9
|
+
UserRoleAssignmentServiceImpl,
|
|
10
|
+
} from './user-role-assignments.js'
|
|
5
11
|
import { type UserService, UserServiceImpl } from './users.js'
|
|
6
12
|
|
|
7
13
|
/**
|
|
@@ -41,11 +47,32 @@ export class AccountClient {
|
|
|
41
47
|
*/
|
|
42
48
|
readonly roles: RoleService
|
|
43
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Service for the org-wide user-role-assignment listing ("who holds which
|
|
52
|
+
* role" in one request).
|
|
53
|
+
*/
|
|
54
|
+
readonly userRoleAssignments: UserRoleAssignmentService
|
|
55
|
+
|
|
44
56
|
/**
|
|
45
57
|
* Service for managing organizations.
|
|
46
58
|
*/
|
|
47
59
|
readonly organizations: OrganizationService
|
|
48
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Service for managing teams and their membership.
|
|
63
|
+
*/
|
|
64
|
+
readonly teams: TeamService
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Service for sharing individual resources with a principal.
|
|
68
|
+
*
|
|
69
|
+
* Lives here rather than on the owning resource's client because sharing is
|
|
70
|
+
* ONE concept across every resource type — a deal, a conversation, a
|
|
71
|
+
* knowledge space. Callers name an entity; which service handles the write is
|
|
72
|
+
* the SDK's problem, so centralizing it server-side later moves no caller.
|
|
73
|
+
*/
|
|
74
|
+
readonly shares: ShareService
|
|
75
|
+
|
|
49
76
|
/**
|
|
50
77
|
* Creates a new AccountClient instance.
|
|
51
78
|
*
|
|
@@ -55,7 +82,10 @@ export class AccountClient {
|
|
|
55
82
|
this.me = new MeServiceImpl(client)
|
|
56
83
|
this.users = new UserServiceImpl(client)
|
|
57
84
|
this.roles = new RoleServiceImpl(client)
|
|
85
|
+
this.userRoleAssignments = new UserRoleAssignmentServiceImpl(client)
|
|
58
86
|
this.organizations = new OrganizationServiceImpl(client)
|
|
87
|
+
this.teams = new TeamServiceImpl(client)
|
|
88
|
+
this.shares = new ShareServiceImpl(client)
|
|
59
89
|
}
|
|
60
90
|
}
|
|
61
91
|
|
|
@@ -63,12 +93,20 @@ export type { MeService } from './me.js'
|
|
|
63
93
|
export type { OrganizationService } from './organizations.js'
|
|
64
94
|
// Re-export platform entities (canonical mirror of the Go registry)
|
|
65
95
|
export type { PlatformEntity } from './platform-entities.js'
|
|
66
|
-
export {
|
|
96
|
+
export {
|
|
97
|
+
isPlatformEntity,
|
|
98
|
+
PLATFORM_ENTITIES,
|
|
99
|
+
PLATFORM_ENTITY_SLUGS,
|
|
100
|
+
SCOPED_PLATFORM_ENTITY_SLUGS,
|
|
101
|
+
} from './platform-entities.js'
|
|
67
102
|
export type { RoleService } from './roles.js'
|
|
103
|
+
export type { TeamService } from './teams.js'
|
|
104
|
+
export type { UserRoleAssignmentService } from './user-role-assignments.js'
|
|
68
105
|
// Re-export types
|
|
69
106
|
export type {
|
|
70
107
|
// Api key types
|
|
71
108
|
ApiKey,
|
|
109
|
+
AddTeamMemberRequest,
|
|
72
110
|
AssignPermissionRequest,
|
|
73
111
|
AssignRoleRequest,
|
|
74
112
|
// Shared types
|
|
@@ -77,10 +115,14 @@ export type {
|
|
|
77
115
|
CreatedApiKey,
|
|
78
116
|
CreateOrganizationRequest,
|
|
79
117
|
CreateRoleRequest,
|
|
118
|
+
CreateTeamRequest,
|
|
80
119
|
CreateUserRequest,
|
|
81
120
|
ListOrganizationsOptions,
|
|
82
121
|
ListRolePermissionsOptions,
|
|
83
122
|
ListRolesOptions,
|
|
123
|
+
ListTeamMembersOptions,
|
|
124
|
+
ListTeamsOptions,
|
|
125
|
+
ListOrgUserRoleAssignmentsOptions,
|
|
84
126
|
ListUserRoleAssignmentsOptions,
|
|
85
127
|
ListUsersOptions,
|
|
86
128
|
// Organization types
|
|
@@ -90,11 +132,20 @@ export type {
|
|
|
90
132
|
// Role types
|
|
91
133
|
Role,
|
|
92
134
|
RoleEntityPermission,
|
|
135
|
+
// Team types
|
|
136
|
+
Team,
|
|
137
|
+
TeamMember,
|
|
138
|
+
UpdateMeRequest,
|
|
93
139
|
UpdateOrganizationRequest,
|
|
94
140
|
UpdateRoleRequest,
|
|
141
|
+
UpdateTeamRequest,
|
|
95
142
|
UpdateUserRequest,
|
|
96
143
|
// User types
|
|
97
144
|
User,
|
|
145
|
+
// Share types
|
|
146
|
+
SharePermission,
|
|
147
|
+
ShareRequest,
|
|
148
|
+
ShareRow,
|
|
98
149
|
// User role assignment types
|
|
99
150
|
UserRoleAssignment,
|
|
100
151
|
} from './types.js'
|
|
@@ -102,10 +153,16 @@ export type {
|
|
|
102
153
|
export {
|
|
103
154
|
ApiKeySchema,
|
|
104
155
|
OrganizationSchema,
|
|
156
|
+
SharePermissionSchema,
|
|
157
|
+
ShareRequestSchema,
|
|
105
158
|
RoleEntityPermissionSchema,
|
|
106
159
|
RoleSchema,
|
|
160
|
+
TeamMemberSchema,
|
|
161
|
+
TeamSchema,
|
|
107
162
|
UserRoleAssignmentSchema,
|
|
108
163
|
UserSchema,
|
|
109
164
|
} from './types.js'
|
|
110
165
|
// Re-export service interfaces
|
|
111
166
|
export type { UserService } from './users.js'
|
|
167
|
+
// Share routing — exported so a UI can hide a share affordance it cannot serve.
|
|
168
|
+
export { isShareable, type ShareService, shareRouteFor } from './shares.js'
|
package/src/auth/me.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ProteosClient } from '../client.js'
|
|
2
|
-
import type { Organization, User } from './types.js'
|
|
2
|
+
import type { Organization, UpdateMeRequest, User } from './types.js'
|
|
3
3
|
|
|
4
4
|
const ME_BASE_PATH = '/accounts/v1/me'
|
|
5
5
|
|
|
@@ -17,6 +17,15 @@ export interface MeService {
|
|
|
17
17
|
*/
|
|
18
18
|
get(): Promise<User>
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Updates the caller's own profile (name, photo). Requires a JWT session —
|
|
22
|
+
* requests authenticated with an API key are rejected.
|
|
23
|
+
*
|
|
24
|
+
* @param request - Fields to update; `profile_image: null` removes the photo
|
|
25
|
+
* @returns The updated user
|
|
26
|
+
*/
|
|
27
|
+
update(request: UpdateMeRequest): Promise<User>
|
|
28
|
+
|
|
20
29
|
/**
|
|
21
30
|
* Lists the organizations the caller can act in (all orgs for a platform
|
|
22
31
|
* admin; otherwise the orgs they're a member of). Powers the org-switcher.
|
|
@@ -36,6 +45,10 @@ export class MeServiceImpl implements MeService {
|
|
|
36
45
|
return this.client.request<User>('GET', ME_BASE_PATH)
|
|
37
46
|
}
|
|
38
47
|
|
|
48
|
+
async update(request: UpdateMeRequest): Promise<User> {
|
|
49
|
+
return this.client.request<User>('PATCH', ME_BASE_PATH, request)
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
async organizations(): Promise<Organization[]> {
|
|
40
53
|
const response = await this.client.request<{ data: Organization[] }>(
|
|
41
54
|
'GET',
|
|
@@ -22,6 +22,12 @@ export const PLATFORM_ENTITIES: readonly PlatformEntity[] = [
|
|
|
22
22
|
{ slug: 'roles', name: 'Roles' },
|
|
23
23
|
{ slug: 'user-role-assignments', name: 'User Role Assignments' },
|
|
24
24
|
{ slug: 'role-entity-permissions', name: 'Role Entity Permissions' },
|
|
25
|
+
// Teams are the org's structure and a principal that can hold access anywhere
|
|
26
|
+
// a user can. Membership is a separate slug: seeing the org chart and changing
|
|
27
|
+
// who is in it are different decisions, and membership is what moves access.
|
|
28
|
+
{ slug: 'teams', name: 'Teams' },
|
|
29
|
+
{ slug: 'team-members', name: 'Team Members' },
|
|
30
|
+
// The share audit trail — a distinct grant from the resources it references.
|
|
25
31
|
// Schema / content (metadata-service)
|
|
26
32
|
{ slug: 'entities', name: 'Entities' },
|
|
27
33
|
{ slug: 'pages', name: 'Pages' },
|
|
@@ -47,6 +53,7 @@ export const PLATFORM_ENTITIES: readonly PlatformEntity[] = [
|
|
|
47
53
|
{ slug: 'knowledge-nodes', name: 'Knowledge Nodes' },
|
|
48
54
|
{ slug: 'knowledge-links', name: 'Knowledge Links' },
|
|
49
55
|
{ slug: 'knowledge-labels', name: 'Knowledge Labels' },
|
|
56
|
+
{ slug: 'knowledge-spaces', name: 'Knowledge Spaces' },
|
|
50
57
|
// Agent suite (agent-service)
|
|
51
58
|
{ slug: 'agents', name: 'Agents' },
|
|
52
59
|
{ slug: 'prompts', name: 'Prompts' },
|
|
@@ -70,6 +77,17 @@ export const PLATFORM_ENTITIES: readonly PlatformEntity[] = [
|
|
|
70
77
|
{ slug: 'glossary-terms', name: 'Glossary Terms' },
|
|
71
78
|
// Mistranscribed terms proposed by the post-transcription review pass.
|
|
72
79
|
{ slug: 'mistranscribed-terms', name: 'Mistranscribed Terms' },
|
|
80
|
+
// The person-level identity layer (contacts + addresses + merges + consent +
|
|
81
|
+
// erasure) — one grant governs the whole aggregate.
|
|
82
|
+
{ slug: 'contacts', name: 'Contacts' },
|
|
83
|
+
// Ingest-time filter rules + their drop-audit events.
|
|
84
|
+
{ slug: 'conversation-filters', name: 'Conversation Filters' },
|
|
85
|
+
// The org's conversation taxonomy (read by the pre-summary classifier).
|
|
86
|
+
{ slug: 'conversation-types', name: 'Conversation Types' },
|
|
87
|
+
// Generic org-shared audience taxonomy; membership lives on the contact.
|
|
88
|
+
{ slug: 'contact-groups', name: 'Contact Groups' },
|
|
89
|
+
// Tone-of-voice synthesis: per-user setups + generated instruction profiles.
|
|
90
|
+
{ slug: 'tone-profiles', name: 'Tone Profiles' },
|
|
73
91
|
// Connectors (connector-service). `connections` above is shared; this is the
|
|
74
92
|
// manifest catalog.
|
|
75
93
|
{ slug: 'connectors', name: 'Connectors' },
|
|
@@ -80,6 +98,15 @@ export const PLATFORM_ENTITY_SLUGS: readonly string[] = PLATFORM_ENTITIES.map(
|
|
|
80
98
|
(entity) => entity.slug,
|
|
81
99
|
)
|
|
82
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Platform entities whose owning service actually enforces the `*_scoped`
|
|
103
|
+
* permission verbs (record-level access grants). Granting `read_scoped` on any
|
|
104
|
+
* other platform entity is legal on the wire but inert — no service narrows
|
|
105
|
+
* by it — so permission UIs only offer the scoped column for these. Extend as
|
|
106
|
+
* services adopt scoped enforcement.
|
|
107
|
+
*/
|
|
108
|
+
export const SCOPED_PLATFORM_ENTITY_SLUGS: readonly string[] = ['knowledge-nodes']
|
|
109
|
+
|
|
83
110
|
/** True when `slug` belongs to a platform entity (reserved; can't be user-created). */
|
|
84
111
|
export function isPlatformEntity(slug: string): boolean {
|
|
85
112
|
return PLATFORM_ENTITY_SLUGS.includes(slug)
|
package/src/auth/roles.ts
CHANGED
|
@@ -93,7 +93,9 @@ export interface RoleService {
|
|
|
93
93
|
* Deletes a role.
|
|
94
94
|
*
|
|
95
95
|
* @param slug - Role slug
|
|
96
|
-
* @throws {ProteosError} If role not found (404)
|
|
96
|
+
* @throws {ProteosError} If role not found (404), or if the role still has
|
|
97
|
+
* entity permissions attached (400 `role_deletion_not_allowed`) — remove
|
|
98
|
+
* the grants first
|
|
97
99
|
*/
|
|
98
100
|
delete(slug: string): Promise<void>
|
|
99
101
|
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { ProteosClient } from '../client.js'
|
|
2
|
+
import type { PrincipalRef } from '../meta/types.js'
|
|
3
|
+
import { ProteosError } from '../errors.js'
|
|
4
|
+
import type { SharePermission, ShareRequest, ShareRow } from './types.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Which service owns each shareable platform entity.
|
|
8
|
+
*
|
|
9
|
+
* Sharing is decided by the service that OWNS the resource — whether a caller
|
|
10
|
+
* may grant depends on what the row itself grants them (its granting
|
|
11
|
+
* attributes, or an owner column), which only that service can read. So the
|
|
12
|
+
* write endpoint is per-service by necessity.
|
|
13
|
+
*
|
|
14
|
+
* This map is the client-side seam that hides that. Callers name an entity, not
|
|
15
|
+
* a service, so if sharing is ever centralized behind one endpoint the change is
|
|
16
|
+
* a one-line edit here and no caller moves.
|
|
17
|
+
*
|
|
18
|
+
* Anything NOT listed is a user-defined entity, which data-service owns — that
|
|
19
|
+
* set is unbounded and appears at runtime, so it is the default rather than an
|
|
20
|
+
* enumeration.
|
|
21
|
+
*/
|
|
22
|
+
const PLATFORM_ENTITY_SERVICE: Readonly<Record<string, string>> = {
|
|
23
|
+
// Agent suite (agent-service)
|
|
24
|
+
agents: '/agents',
|
|
25
|
+
prompts: '/agents',
|
|
26
|
+
skills: '/agents',
|
|
27
|
+
tools: '/agents',
|
|
28
|
+
toolsets: '/agents',
|
|
29
|
+
'mcp-servers': '/agents',
|
|
30
|
+
// Knowledge (knowledge-service)
|
|
31
|
+
'knowledge-spaces': '/knowledge',
|
|
32
|
+
'knowledge-nodes': '/knowledge',
|
|
33
|
+
// Conversations (conversation-service)
|
|
34
|
+
conversations: '/conversations',
|
|
35
|
+
connections: '/conversations',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The services that actually serve `/v1/shares` today.
|
|
40
|
+
*
|
|
41
|
+
* Deliberately separate from the map above: the other services get the endpoint
|
|
42
|
+
* in later phases, and until then a share must fail with a clear message rather
|
|
43
|
+
* than a bare 404 that reads like the record does not exist.
|
|
44
|
+
*/
|
|
45
|
+
const SERVICES_WITH_SHARING: ReadonlySet<string> = new Set(['/data', '/knowledge'])
|
|
46
|
+
|
|
47
|
+
/** Records live in data-service; user-defined entities are the unlisted majority. */
|
|
48
|
+
const DEFAULT_SERVICE = '/data'
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Resolves the share endpoint for an entity slug.
|
|
52
|
+
*
|
|
53
|
+
* Exported for tests and for callers that want to check support before offering
|
|
54
|
+
* a share affordance in a UI.
|
|
55
|
+
*/
|
|
56
|
+
export function shareRouteFor(entitySlug: string): string {
|
|
57
|
+
const service = PLATFORM_ENTITY_SERVICE[entitySlug] ?? DEFAULT_SERVICE
|
|
58
|
+
if (!SERVICES_WITH_SHARING.has(service)) {
|
|
59
|
+
throw new ProteosError(
|
|
60
|
+
`Sharing is not available for '${entitySlug}' yet — ${service.slice(1)}-service does not serve a share endpoint.`,
|
|
61
|
+
501,
|
|
62
|
+
'sharing_not_supported',
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
return `${service}/v1/shares`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** True when `entitySlug` can be shared today. */
|
|
69
|
+
export function isShareable(entitySlug: string): boolean {
|
|
70
|
+
const service = PLATFORM_ENTITY_SERVICE[entitySlug] ?? DEFAULT_SERVICE
|
|
71
|
+
return SERVICES_WITH_SHARING.has(service)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Sharing an individual resource with a principal — a person, an agent, an API
|
|
76
|
+
* client, a team, or the whole organization.
|
|
77
|
+
*
|
|
78
|
+
* A share grants access to ONE resource, which is a different thing from a role:
|
|
79
|
+
* a role grants a permission on an entity TYPE (every deal), a share grants it
|
|
80
|
+
* on an INSTANCE (this deal). The two compose — a share gives nothing to someone
|
|
81
|
+
* holding no grant on the entity type at all, which bounds its blast radius to a
|
|
82
|
+
* population already authorized for that kind of resource.
|
|
83
|
+
*
|
|
84
|
+
* Callers name an ENTITY, never a service. Which service handles the write is
|
|
85
|
+
* this module's problem.
|
|
86
|
+
*
|
|
87
|
+
* Who may share: the resource's owner, or someone holding the plain (unscoped)
|
|
88
|
+
* permission on the entity. Notably NOT a share recipient — access held only
|
|
89
|
+
* through a share cannot be passed on, and cannot be turned into ownership.
|
|
90
|
+
*/
|
|
91
|
+
export interface ShareService {
|
|
92
|
+
/**
|
|
93
|
+
* Grants a principal access to one resource.
|
|
94
|
+
*
|
|
95
|
+
* Granting `write` or `delete` implies `read`: a principal who cannot see a
|
|
96
|
+
* resource must never be able to edit it, so the read is granted alongside.
|
|
97
|
+
*
|
|
98
|
+
* @throws {ProteosError} 403 `share_not_permitted` when the caller holds their
|
|
99
|
+
* own access only through a share; 403 `principal_not_in_org` when the
|
|
100
|
+
* recipient is outside the caller's organization (rejected outright rather
|
|
101
|
+
* than written inert, which would become a live grant the moment they joined);
|
|
102
|
+
* 404 when the resource does not exist or is not visible to the caller;
|
|
103
|
+
* 501 `sharing_not_supported` when the owning service has no share endpoint.
|
|
104
|
+
*/
|
|
105
|
+
share(
|
|
106
|
+
entitySlug: string,
|
|
107
|
+
recordId: string,
|
|
108
|
+
principal: PrincipalRef,
|
|
109
|
+
permission: SharePermission,
|
|
110
|
+
): Promise<void>
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Revokes a principal's access to one resource.
|
|
114
|
+
*
|
|
115
|
+
* Revoking `read` CASCADES to `write` and `delete` — an edit right on
|
|
116
|
+
* something someone can no longer see would be incoherent.
|
|
117
|
+
*
|
|
118
|
+
* Revocation is deliberately wider than granting: the owner, anyone holding
|
|
119
|
+
* the unscoped `write` permission, and platform admins may all revoke. Erring
|
|
120
|
+
* permissive costs nothing here, while erring strict would strand grants
|
|
121
|
+
* nobody could clean up.
|
|
122
|
+
*/
|
|
123
|
+
unshare(
|
|
124
|
+
entitySlug: string,
|
|
125
|
+
recordId: string,
|
|
126
|
+
principal: PrincipalRef,
|
|
127
|
+
permission: SharePermission,
|
|
128
|
+
): Promise<void>
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Lists a resource's shares.
|
|
132
|
+
*
|
|
133
|
+
* Gated to the population that may REVOKE them — the owner, unscoped-write
|
|
134
|
+
* holders, and platform admins. Record read is deliberately not enough: a
|
|
135
|
+
* share list discloses org structure ("this deal is visible to team:legal").
|
|
136
|
+
*
|
|
137
|
+
* @throws {ProteosError} 403 `share_listing_not_permitted` otherwise.
|
|
138
|
+
*/
|
|
139
|
+
listShares(entitySlug: string, recordId: string): Promise<ShareRow[]>
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export class ShareServiceImpl implements ShareService {
|
|
143
|
+
constructor(private readonly client: ProteosClient) {}
|
|
144
|
+
|
|
145
|
+
async share(
|
|
146
|
+
entitySlug: string,
|
|
147
|
+
recordId: string,
|
|
148
|
+
principal: PrincipalRef,
|
|
149
|
+
permission: SharePermission,
|
|
150
|
+
): Promise<void> {
|
|
151
|
+
await this.client.request<void>('POST', shareRouteFor(entitySlug), {
|
|
152
|
+
entity_slug: entitySlug,
|
|
153
|
+
record_id: recordId,
|
|
154
|
+
principal,
|
|
155
|
+
permission,
|
|
156
|
+
} satisfies ShareRequest)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async unshare(
|
|
160
|
+
entitySlug: string,
|
|
161
|
+
recordId: string,
|
|
162
|
+
principal: PrincipalRef,
|
|
163
|
+
permission: SharePermission,
|
|
164
|
+
): Promise<void> {
|
|
165
|
+
await this.client.request<void>('DELETE', shareRouteFor(entitySlug), {
|
|
166
|
+
entity_slug: entitySlug,
|
|
167
|
+
record_id: recordId,
|
|
168
|
+
principal,
|
|
169
|
+
permission,
|
|
170
|
+
} satisfies ShareRequest)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async listShares(entitySlug: string, recordId: string): Promise<ShareRow[]> {
|
|
174
|
+
const query = new URLSearchParams({ entity_slug: entitySlug, record_id: recordId })
|
|
175
|
+
const response = await this.client.request<{ shares: ShareRow[] }>(
|
|
176
|
+
'GET',
|
|
177
|
+
`${shareRouteFor(entitySlug)}?${query.toString()}`,
|
|
178
|
+
)
|
|
179
|
+
return response.shares ?? []
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { ProteosClient } from '../client.js'
|
|
2
|
+
import { PageIterator } from '../iterator.js'
|
|
3
|
+
import type { ListResult } from '../types/common.js'
|
|
4
|
+
import type {
|
|
5
|
+
AddTeamMemberRequest,
|
|
6
|
+
CreateTeamRequest,
|
|
7
|
+
ListTeamMembersOptions,
|
|
8
|
+
ListTeamsOptions,
|
|
9
|
+
Team,
|
|
10
|
+
TeamMember,
|
|
11
|
+
UpdateTeamRequest,
|
|
12
|
+
} from './types.js'
|
|
13
|
+
|
|
14
|
+
const TEAMS_BASE_PATH = '/accounts/v1/teams'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Service for managing teams — the org's structure, and a principal that can
|
|
18
|
+
* hold access anywhere a user can.
|
|
19
|
+
*
|
|
20
|
+
* Team hierarchy rolls UP: a member of a child team counts as a member of every
|
|
21
|
+
* ancestor **for grants made to that ancestor**. Granting `sales` reaches the
|
|
22
|
+
* members of `sales-emea`; granting `sales-emea` never reaches someone who is
|
|
23
|
+
* only in `sales`. This is the direction people get backwards.
|
|
24
|
+
*/
|
|
25
|
+
export interface TeamService {
|
|
26
|
+
/**
|
|
27
|
+
* Lists teams with optional filtering.
|
|
28
|
+
* Returns an async iterator that automatically handles pagination.
|
|
29
|
+
*/
|
|
30
|
+
list(options?: ListTeamsOptions): PageIterator<Team, ListTeamsOptions>
|
|
31
|
+
|
|
32
|
+
/** Fetches a single page of teams. */
|
|
33
|
+
listPage(options?: ListTeamsOptions): Promise<ListResult<Team>>
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Gets a single team by slug.
|
|
37
|
+
* @throws {ProteosError} If the team is not found (404)
|
|
38
|
+
*/
|
|
39
|
+
get(slug: string): Promise<Team>
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates a team, optionally nested under an existing one.
|
|
43
|
+
* @throws {ProteosError} If the parent would create a cycle or exceed the
|
|
44
|
+
* maximum depth (400 `team_hierarchy_invalid`)
|
|
45
|
+
*/
|
|
46
|
+
create(request: CreateTeamRequest): Promise<Team>
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Updates a team's name, description or parent.
|
|
50
|
+
*
|
|
51
|
+
* The slug cannot be changed: it is half the primary key and every grant
|
|
52
|
+
* references `team:<orgId>/<slug>`, so a rename would orphan them. Pass an
|
|
53
|
+
* empty `parent_team_slug` to promote the team to a root.
|
|
54
|
+
*/
|
|
55
|
+
update(slug: string, request: UpdateTeamRequest): Promise<Team>
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Deletes a team.
|
|
59
|
+
* @throws {ProteosError} If the team still has members or child teams
|
|
60
|
+
* (400 `team_deletion_not_allowed`)
|
|
61
|
+
*/
|
|
62
|
+
delete(slug: string): Promise<void>
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Lists a team's DIRECT members. Membership of ancestor teams is derived, not
|
|
66
|
+
* stored, so it does not appear here.
|
|
67
|
+
*/
|
|
68
|
+
getMembers(
|
|
69
|
+
slug: string,
|
|
70
|
+
options?: ListTeamMembersOptions,
|
|
71
|
+
): PageIterator<TeamMember, ListTeamMembersOptions>
|
|
72
|
+
|
|
73
|
+
/** Adds a user to a team. */
|
|
74
|
+
addMember(slug: string, request: AddTeamMemberRequest): Promise<TeamMember>
|
|
75
|
+
|
|
76
|
+
/** Removes a user from a team. */
|
|
77
|
+
removeMember(slug: string, userId: string): Promise<void>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Implementation of TeamService.
|
|
82
|
+
*/
|
|
83
|
+
export class TeamServiceImpl implements TeamService {
|
|
84
|
+
constructor(private readonly client: ProteosClient) {}
|
|
85
|
+
|
|
86
|
+
list(options: ListTeamsOptions = {}): PageIterator<Team, ListTeamsOptions> {
|
|
87
|
+
return new PageIterator((opts) => this.listPage(opts), options)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async listPage(options: ListTeamsOptions = {}): Promise<ListResult<Team>> {
|
|
91
|
+
return this.client.requestWithQuery<ListResult<Team>>('GET', TEAMS_BASE_PATH, options)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async get(slug: string): Promise<Team> {
|
|
95
|
+
return this.client.request<Team>('GET', `${TEAMS_BASE_PATH}/${slug}`)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async create(request: CreateTeamRequest): Promise<Team> {
|
|
99
|
+
return this.client.request<Team>('POST', TEAMS_BASE_PATH, request)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async update(slug: string, request: UpdateTeamRequest): Promise<Team> {
|
|
103
|
+
return this.client.request<Team>('PATCH', `${TEAMS_BASE_PATH}/${slug}`, request)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async delete(slug: string): Promise<void> {
|
|
107
|
+
await this.client.request<void>('DELETE', `${TEAMS_BASE_PATH}/${slug}`)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
getMembers(
|
|
111
|
+
slug: string,
|
|
112
|
+
options: ListTeamMembersOptions = {},
|
|
113
|
+
): PageIterator<TeamMember, ListTeamMembersOptions> {
|
|
114
|
+
return new PageIterator((opts) => this.fetchMembersPage(slug, opts), options)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async addMember(slug: string, request: AddTeamMemberRequest): Promise<TeamMember> {
|
|
118
|
+
return this.client.request<TeamMember>('POST', `${TEAMS_BASE_PATH}/${slug}/members`, request)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async removeMember(slug: string, userId: string): Promise<void> {
|
|
122
|
+
await this.client.request<void>('DELETE', `${TEAMS_BASE_PATH}/${slug}/members/${userId}`)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private async fetchMembersPage(
|
|
126
|
+
slug: string,
|
|
127
|
+
options: ListTeamMembersOptions,
|
|
128
|
+
): Promise<ListResult<TeamMember>> {
|
|
129
|
+
return this.client.requestWithQuery<ListResult<TeamMember>>(
|
|
130
|
+
'GET',
|
|
131
|
+
`${TEAMS_BASE_PATH}/${slug}/members`,
|
|
132
|
+
options,
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
}
|