@meetreeve/module-team 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -2
- package/dist/client/index.d.mts +141 -0
- package/dist/client/index.d.ts +141 -0
- package/dist/client/index.js +100 -0
- package/dist/client/index.mjs +74 -0
- package/dist/config/index.d.mts +159 -0
- package/dist/config/index.d.ts +159 -0
- package/dist/config/index.js +770 -0
- package/dist/config/index.mjs +744 -0
- package/dist/index.d.mts +141 -0
- package/dist/index.d.ts +141 -0
- package/dist/index.js +102 -0
- package/dist/index.mjs +74 -0
- package/package.json +70 -4
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Roles the org-members router knows. `billing_manager` is a legacy alias
|
|
3
|
+
* the backend still accepts; no surface offers it from the invite dialog.
|
|
4
|
+
* Rows may still carry a role this union doesn't name, so row fields are
|
|
5
|
+
* typed `string` and the panel renders unknown roles verbatim.
|
|
6
|
+
*/
|
|
7
|
+
type TeamRole = "owner" | "admin" | "billing_manager" | "member" | "viewer";
|
|
8
|
+
/** A role the invite dialog and the role picker can hand out (never `owner`). */
|
|
9
|
+
type AssignableRole = Exclude<TeamRole, "owner" | "billing_manager">;
|
|
10
|
+
/** One live member of the org (`OrganizationMember` row). `GET /api/org/members`. */
|
|
11
|
+
interface TeamMember {
|
|
12
|
+
/** OrganizationMember.id — the id every member route takes. */
|
|
13
|
+
id: string;
|
|
14
|
+
user_id: string;
|
|
15
|
+
email: string | null;
|
|
16
|
+
role: string;
|
|
17
|
+
is_default: boolean;
|
|
18
|
+
/** ISO-8601. */
|
|
19
|
+
created_at: string;
|
|
20
|
+
}
|
|
21
|
+
/** One invitation record. `GET /api/org/invitations`. */
|
|
22
|
+
interface Invitation {
|
|
23
|
+
id: string;
|
|
24
|
+
email: string;
|
|
25
|
+
role: string;
|
|
26
|
+
invited_by_email: string | null;
|
|
27
|
+
/** pending | accepted | expired | revoked */
|
|
28
|
+
status: string;
|
|
29
|
+
/** ISO-8601. */
|
|
30
|
+
created_at: string;
|
|
31
|
+
/** ISO-8601. */
|
|
32
|
+
expires_at: string;
|
|
33
|
+
accepted_at: string | null;
|
|
34
|
+
/**
|
|
35
|
+
* Spaces the invitee joins on accept (DEV-15988 / reeve-services DEV-15559).
|
|
36
|
+
* The backend always sends it; optional so a backend that predates it
|
|
37
|
+
* still type-checks as "no pre-set spaces".
|
|
38
|
+
*/
|
|
39
|
+
spaces?: InvitationSpace[];
|
|
40
|
+
}
|
|
41
|
+
/** A space role. */
|
|
42
|
+
type SpaceRole = "member" | "admin";
|
|
43
|
+
/** One pre-set space on an invitation (`GET /api/org/invitations` item). */
|
|
44
|
+
interface InvitationSpace {
|
|
45
|
+
id: string;
|
|
46
|
+
name: string;
|
|
47
|
+
role: SpaceRole;
|
|
48
|
+
}
|
|
49
|
+
/** One space an invite (or a pending invitation) should land the invitee in. */
|
|
50
|
+
interface InviteSpace {
|
|
51
|
+
space_id: string;
|
|
52
|
+
role: SpaceRole;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* One of the org's spaces, as the invite dialog's Spaces picker lists it
|
|
56
|
+
* (reeve-frontend maps `GET /api/org/rbac/spaces` onto this). Every space is
|
|
57
|
+
* offered, the default one included: nobody joins a space automatically
|
|
58
|
+
* (DEV-16808). `can_manage: false` rows are shown disabled.
|
|
59
|
+
*/
|
|
60
|
+
interface SpaceOption {
|
|
61
|
+
id: string;
|
|
62
|
+
name: string;
|
|
63
|
+
kind?: string;
|
|
64
|
+
is_default: boolean;
|
|
65
|
+
can_manage: boolean;
|
|
66
|
+
}
|
|
67
|
+
/** `POST /api/org/invite`. */
|
|
68
|
+
interface InviteMemberResponse {
|
|
69
|
+
message: string;
|
|
70
|
+
email: string;
|
|
71
|
+
role: string;
|
|
72
|
+
invitation_id?: string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The data-access boundary the panel is built against. Every method takes
|
|
76
|
+
* the org it acts on: there is no ambient "current org" anywhere in this
|
|
77
|
+
* package, so a request can never be issued for an org other than the one
|
|
78
|
+
* the caller named (DEV-8592). `createTeamClient` is the reference
|
|
79
|
+
* implementation; a host may supply its own.
|
|
80
|
+
*/
|
|
81
|
+
interface TeamClient {
|
|
82
|
+
listMembers(orgId: string): Promise<TeamMember[]>;
|
|
83
|
+
updateMemberRole(orgId: string, memberId: string, role: string): Promise<void>;
|
|
84
|
+
removeMember(orgId: string, memberId: string): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* `spaces` goes in the body whenever it is passed, `[]` included (a
|
|
87
|
+
* one-space org sends `[]`, DEV-16808); omitted keeps the pre-DEV-15988
|
|
88
|
+
* request.
|
|
89
|
+
*/
|
|
90
|
+
inviteMember(orgId: string, email: string, role: string, spaces?: InviteSpace[]): Promise<InviteMemberResponse>;
|
|
91
|
+
listInvitations(orgId: string): Promise<Invitation[]>;
|
|
92
|
+
revokeInvitation(orgId: string, invitationId: string): Promise<void>;
|
|
93
|
+
resendInvitation(orgId: string, invitationId: string): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Framework-agnostic Reeve.Team client (DEV-9998, C2 of epic DEV-9983).
|
|
98
|
+
*
|
|
99
|
+
* The server-safe half: no React, no Next.js. The only import is type-only,
|
|
100
|
+
* erased at build time.
|
|
101
|
+
*
|
|
102
|
+
* Contract: reeve-services' org-members router, ported route-for-route from
|
|
103
|
+
* reeve-frontend's `src/lib/api/team.ts`:
|
|
104
|
+
*
|
|
105
|
+
* GET /api/org/members -> TeamMember[]
|
|
106
|
+
* PUT /api/org/members/{id}/role {role} -> 2xx
|
|
107
|
+
* DELETE /api/org/members/{id} -> 2xx
|
|
108
|
+
* POST /api/org/invite {email, role, spaces?} -> InviteMemberResponse
|
|
109
|
+
* GET /api/org/invitations -> Invitation[]
|
|
110
|
+
* DELETE /api/org/invitations/{id} -> 2xx
|
|
111
|
+
* POST /api/org/invitations/{id}/resend -> 2xx (resets the 7-day expiry)
|
|
112
|
+
*
|
|
113
|
+
* Transport is the HOST's: `createTeamClient({ fetch })` takes a JSON
|
|
114
|
+
* fetcher (reeve-frontend passes `productFetch`, which owns auth, 401
|
|
115
|
+
* refresh and timeouts). The org is never ambient. Every method takes the
|
|
116
|
+
* org it acts on and stamps it as `X-Reeve-Org-Id`, the header the backend's
|
|
117
|
+
* tenant resolver reads, so a request is keyed on the org its caller named
|
|
118
|
+
* rather than on whatever org a global pin held when it went out (DEV-8592).
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
/** The request header the backend tenant resolver reads the org assertion from. */
|
|
122
|
+
declare const ORG_HEADER = "X-Reeve-Org-Id";
|
|
123
|
+
/** What the client hands the host fetcher. `path` is relative to the API origin. */
|
|
124
|
+
type TeamRequestInit = {
|
|
125
|
+
method: "GET" | "POST" | "PUT" | "DELETE";
|
|
126
|
+
headers: Record<string, string>;
|
|
127
|
+
/** JSON-encoded, present only on requests that carry a body. */
|
|
128
|
+
body?: string;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* A host-supplied JSON fetcher: resolve with the parsed response body (any
|
|
132
|
+
* value for an empty 2xx), reject on a non-2xx. reeve-frontend's
|
|
133
|
+
* `productFetch` has exactly this shape.
|
|
134
|
+
*/
|
|
135
|
+
type TeamFetch = (path: string, init: TeamRequestInit) => Promise<unknown>;
|
|
136
|
+
type TeamClientOptions = {
|
|
137
|
+
fetch: TeamFetch;
|
|
138
|
+
};
|
|
139
|
+
declare function createTeamClient({ fetch }: TeamClientOptions): TeamClient;
|
|
140
|
+
|
|
141
|
+
export { type AssignableRole, type Invitation, type InvitationSpace, type InviteMemberResponse, type InviteSpace, ORG_HEADER, type SpaceOption, type SpaceRole, type TeamClient, type TeamClientOptions, type TeamFetch, type TeamMember, type TeamRequestInit, type TeamRole, createTeamClient };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Roles the org-members router knows. `billing_manager` is a legacy alias
|
|
3
|
+
* the backend still accepts; no surface offers it from the invite dialog.
|
|
4
|
+
* Rows may still carry a role this union doesn't name, so row fields are
|
|
5
|
+
* typed `string` and the panel renders unknown roles verbatim.
|
|
6
|
+
*/
|
|
7
|
+
type TeamRole = "owner" | "admin" | "billing_manager" | "member" | "viewer";
|
|
8
|
+
/** A role the invite dialog and the role picker can hand out (never `owner`). */
|
|
9
|
+
type AssignableRole = Exclude<TeamRole, "owner" | "billing_manager">;
|
|
10
|
+
/** One live member of the org (`OrganizationMember` row). `GET /api/org/members`. */
|
|
11
|
+
interface TeamMember {
|
|
12
|
+
/** OrganizationMember.id — the id every member route takes. */
|
|
13
|
+
id: string;
|
|
14
|
+
user_id: string;
|
|
15
|
+
email: string | null;
|
|
16
|
+
role: string;
|
|
17
|
+
is_default: boolean;
|
|
18
|
+
/** ISO-8601. */
|
|
19
|
+
created_at: string;
|
|
20
|
+
}
|
|
21
|
+
/** One invitation record. `GET /api/org/invitations`. */
|
|
22
|
+
interface Invitation {
|
|
23
|
+
id: string;
|
|
24
|
+
email: string;
|
|
25
|
+
role: string;
|
|
26
|
+
invited_by_email: string | null;
|
|
27
|
+
/** pending | accepted | expired | revoked */
|
|
28
|
+
status: string;
|
|
29
|
+
/** ISO-8601. */
|
|
30
|
+
created_at: string;
|
|
31
|
+
/** ISO-8601. */
|
|
32
|
+
expires_at: string;
|
|
33
|
+
accepted_at: string | null;
|
|
34
|
+
/**
|
|
35
|
+
* Spaces the invitee joins on accept (DEV-15988 / reeve-services DEV-15559).
|
|
36
|
+
* The backend always sends it; optional so a backend that predates it
|
|
37
|
+
* still type-checks as "no pre-set spaces".
|
|
38
|
+
*/
|
|
39
|
+
spaces?: InvitationSpace[];
|
|
40
|
+
}
|
|
41
|
+
/** A space role. */
|
|
42
|
+
type SpaceRole = "member" | "admin";
|
|
43
|
+
/** One pre-set space on an invitation (`GET /api/org/invitations` item). */
|
|
44
|
+
interface InvitationSpace {
|
|
45
|
+
id: string;
|
|
46
|
+
name: string;
|
|
47
|
+
role: SpaceRole;
|
|
48
|
+
}
|
|
49
|
+
/** One space an invite (or a pending invitation) should land the invitee in. */
|
|
50
|
+
interface InviteSpace {
|
|
51
|
+
space_id: string;
|
|
52
|
+
role: SpaceRole;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* One of the org's spaces, as the invite dialog's Spaces picker lists it
|
|
56
|
+
* (reeve-frontend maps `GET /api/org/rbac/spaces` onto this). Every space is
|
|
57
|
+
* offered, the default one included: nobody joins a space automatically
|
|
58
|
+
* (DEV-16808). `can_manage: false` rows are shown disabled.
|
|
59
|
+
*/
|
|
60
|
+
interface SpaceOption {
|
|
61
|
+
id: string;
|
|
62
|
+
name: string;
|
|
63
|
+
kind?: string;
|
|
64
|
+
is_default: boolean;
|
|
65
|
+
can_manage: boolean;
|
|
66
|
+
}
|
|
67
|
+
/** `POST /api/org/invite`. */
|
|
68
|
+
interface InviteMemberResponse {
|
|
69
|
+
message: string;
|
|
70
|
+
email: string;
|
|
71
|
+
role: string;
|
|
72
|
+
invitation_id?: string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The data-access boundary the panel is built against. Every method takes
|
|
76
|
+
* the org it acts on: there is no ambient "current org" anywhere in this
|
|
77
|
+
* package, so a request can never be issued for an org other than the one
|
|
78
|
+
* the caller named (DEV-8592). `createTeamClient` is the reference
|
|
79
|
+
* implementation; a host may supply its own.
|
|
80
|
+
*/
|
|
81
|
+
interface TeamClient {
|
|
82
|
+
listMembers(orgId: string): Promise<TeamMember[]>;
|
|
83
|
+
updateMemberRole(orgId: string, memberId: string, role: string): Promise<void>;
|
|
84
|
+
removeMember(orgId: string, memberId: string): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* `spaces` goes in the body whenever it is passed, `[]` included (a
|
|
87
|
+
* one-space org sends `[]`, DEV-16808); omitted keeps the pre-DEV-15988
|
|
88
|
+
* request.
|
|
89
|
+
*/
|
|
90
|
+
inviteMember(orgId: string, email: string, role: string, spaces?: InviteSpace[]): Promise<InviteMemberResponse>;
|
|
91
|
+
listInvitations(orgId: string): Promise<Invitation[]>;
|
|
92
|
+
revokeInvitation(orgId: string, invitationId: string): Promise<void>;
|
|
93
|
+
resendInvitation(orgId: string, invitationId: string): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Framework-agnostic Reeve.Team client (DEV-9998, C2 of epic DEV-9983).
|
|
98
|
+
*
|
|
99
|
+
* The server-safe half: no React, no Next.js. The only import is type-only,
|
|
100
|
+
* erased at build time.
|
|
101
|
+
*
|
|
102
|
+
* Contract: reeve-services' org-members router, ported route-for-route from
|
|
103
|
+
* reeve-frontend's `src/lib/api/team.ts`:
|
|
104
|
+
*
|
|
105
|
+
* GET /api/org/members -> TeamMember[]
|
|
106
|
+
* PUT /api/org/members/{id}/role {role} -> 2xx
|
|
107
|
+
* DELETE /api/org/members/{id} -> 2xx
|
|
108
|
+
* POST /api/org/invite {email, role, spaces?} -> InviteMemberResponse
|
|
109
|
+
* GET /api/org/invitations -> Invitation[]
|
|
110
|
+
* DELETE /api/org/invitations/{id} -> 2xx
|
|
111
|
+
* POST /api/org/invitations/{id}/resend -> 2xx (resets the 7-day expiry)
|
|
112
|
+
*
|
|
113
|
+
* Transport is the HOST's: `createTeamClient({ fetch })` takes a JSON
|
|
114
|
+
* fetcher (reeve-frontend passes `productFetch`, which owns auth, 401
|
|
115
|
+
* refresh and timeouts). The org is never ambient. Every method takes the
|
|
116
|
+
* org it acts on and stamps it as `X-Reeve-Org-Id`, the header the backend's
|
|
117
|
+
* tenant resolver reads, so a request is keyed on the org its caller named
|
|
118
|
+
* rather than on whatever org a global pin held when it went out (DEV-8592).
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
/** The request header the backend tenant resolver reads the org assertion from. */
|
|
122
|
+
declare const ORG_HEADER = "X-Reeve-Org-Id";
|
|
123
|
+
/** What the client hands the host fetcher. `path` is relative to the API origin. */
|
|
124
|
+
type TeamRequestInit = {
|
|
125
|
+
method: "GET" | "POST" | "PUT" | "DELETE";
|
|
126
|
+
headers: Record<string, string>;
|
|
127
|
+
/** JSON-encoded, present only on requests that carry a body. */
|
|
128
|
+
body?: string;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* A host-supplied JSON fetcher: resolve with the parsed response body (any
|
|
132
|
+
* value for an empty 2xx), reject on a non-2xx. reeve-frontend's
|
|
133
|
+
* `productFetch` has exactly this shape.
|
|
134
|
+
*/
|
|
135
|
+
type TeamFetch = (path: string, init: TeamRequestInit) => Promise<unknown>;
|
|
136
|
+
type TeamClientOptions = {
|
|
137
|
+
fetch: TeamFetch;
|
|
138
|
+
};
|
|
139
|
+
declare function createTeamClient({ fetch }: TeamClientOptions): TeamClient;
|
|
140
|
+
|
|
141
|
+
export { type AssignableRole, type Invitation, type InvitationSpace, type InviteMemberResponse, type InviteSpace, ORG_HEADER, type SpaceOption, type SpaceRole, type TeamClient, type TeamClientOptions, type TeamFetch, type TeamMember, type TeamRequestInit, type TeamRole, createTeamClient };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
ORG_HEADER: () => ORG_HEADER,
|
|
24
|
+
createTeamClient: () => createTeamClient
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(src_exports);
|
|
27
|
+
|
|
28
|
+
// src/client.ts
|
|
29
|
+
var ORG_HEADER = "X-Reeve-Org-Id";
|
|
30
|
+
var ORG_ID_SHAPE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
31
|
+
function orgHeaders(orgId) {
|
|
32
|
+
if (typeof orgId !== "string" || !ORG_ID_SHAPE.test(orgId)) {
|
|
33
|
+
throw new Error(`orgId must be a plausible org id, got ${JSON.stringify(orgId)}`);
|
|
34
|
+
}
|
|
35
|
+
return { [ORG_HEADER]: orgId };
|
|
36
|
+
}
|
|
37
|
+
function segment(value, name) {
|
|
38
|
+
if (typeof value !== "string" || value === "") {
|
|
39
|
+
throw new Error(`${name} must be a non-empty string`);
|
|
40
|
+
}
|
|
41
|
+
return encodeURIComponent(value);
|
|
42
|
+
}
|
|
43
|
+
function hasStringId(row) {
|
|
44
|
+
return typeof row === "object" && row !== null && typeof row.id === "string" && row.id !== "";
|
|
45
|
+
}
|
|
46
|
+
function rows(data, what) {
|
|
47
|
+
if (!Array.isArray(data) || !data.every(hasStringId)) {
|
|
48
|
+
throw new Error(`team: malformed ${what} response`);
|
|
49
|
+
}
|
|
50
|
+
return data;
|
|
51
|
+
}
|
|
52
|
+
function createTeamClient({ fetch }) {
|
|
53
|
+
if (typeof fetch !== "function") {
|
|
54
|
+
throw new Error("createTeamClient: `fetch` must be a function");
|
|
55
|
+
}
|
|
56
|
+
const send = (orgId, method, path, body) => {
|
|
57
|
+
const headers = orgHeaders(orgId);
|
|
58
|
+
const init = { method, headers };
|
|
59
|
+
if (body !== void 0) {
|
|
60
|
+
headers["Content-Type"] = "application/json";
|
|
61
|
+
init.body = JSON.stringify(body);
|
|
62
|
+
}
|
|
63
|
+
return fetch(path, init);
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
listMembers: (orgId) => send(orgId, "GET", "/api/org/members").then((d) => rows(d, "members")),
|
|
67
|
+
updateMemberRole: (orgId, memberId, role) => {
|
|
68
|
+
segment(role, "role");
|
|
69
|
+
return send(orgId, "PUT", `/api/org/members/${segment(memberId, "memberId")}/role`, {
|
|
70
|
+
role
|
|
71
|
+
}).then(() => void 0);
|
|
72
|
+
},
|
|
73
|
+
removeMember: (orgId, memberId) => send(orgId, "DELETE", `/api/org/members/${segment(memberId, "memberId")}`).then(
|
|
74
|
+
() => void 0
|
|
75
|
+
),
|
|
76
|
+
inviteMember: (orgId, email, role, spaces) => {
|
|
77
|
+
if (typeof email !== "string" || email.trim() === "") {
|
|
78
|
+
throw new Error("email must be a non-empty string");
|
|
79
|
+
}
|
|
80
|
+
segment(role, "role");
|
|
81
|
+
const body = { email: email.trim(), role };
|
|
82
|
+
if (spaces) body.spaces = spaces;
|
|
83
|
+
return send(orgId, "POST", "/api/org/invite", body).then(
|
|
84
|
+
(d) => d
|
|
85
|
+
);
|
|
86
|
+
},
|
|
87
|
+
listInvitations: (orgId) => send(orgId, "GET", "/api/org/invitations").then((d) => rows(d, "invitations")),
|
|
88
|
+
revokeInvitation: (orgId, invitationId) => send(orgId, "DELETE", `/api/org/invitations/${segment(invitationId, "invitationId")}`).then(
|
|
89
|
+
() => void 0
|
|
90
|
+
),
|
|
91
|
+
resendInvitation: (orgId, invitationId) => send(
|
|
92
|
+
orgId,
|
|
93
|
+
"POST",
|
|
94
|
+
`/api/org/invitations/${segment(invitationId, "invitationId")}/resend`
|
|
95
|
+
).then(() => void 0)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
99
|
+
0 && (module.exports = {
|
|
100
|
+
ORG_HEADER,
|
|
101
|
+
createTeamClient
|
|
102
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
var ORG_HEADER = "X-Reeve-Org-Id";
|
|
3
|
+
var ORG_ID_SHAPE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
4
|
+
function orgHeaders(orgId) {
|
|
5
|
+
if (typeof orgId !== "string" || !ORG_ID_SHAPE.test(orgId)) {
|
|
6
|
+
throw new Error(`orgId must be a plausible org id, got ${JSON.stringify(orgId)}`);
|
|
7
|
+
}
|
|
8
|
+
return { [ORG_HEADER]: orgId };
|
|
9
|
+
}
|
|
10
|
+
function segment(value, name) {
|
|
11
|
+
if (typeof value !== "string" || value === "") {
|
|
12
|
+
throw new Error(`${name} must be a non-empty string`);
|
|
13
|
+
}
|
|
14
|
+
return encodeURIComponent(value);
|
|
15
|
+
}
|
|
16
|
+
function hasStringId(row) {
|
|
17
|
+
return typeof row === "object" && row !== null && typeof row.id === "string" && row.id !== "";
|
|
18
|
+
}
|
|
19
|
+
function rows(data, what) {
|
|
20
|
+
if (!Array.isArray(data) || !data.every(hasStringId)) {
|
|
21
|
+
throw new Error(`team: malformed ${what} response`);
|
|
22
|
+
}
|
|
23
|
+
return data;
|
|
24
|
+
}
|
|
25
|
+
function createTeamClient({ fetch }) {
|
|
26
|
+
if (typeof fetch !== "function") {
|
|
27
|
+
throw new Error("createTeamClient: `fetch` must be a function");
|
|
28
|
+
}
|
|
29
|
+
const send = (orgId, method, path, body) => {
|
|
30
|
+
const headers = orgHeaders(orgId);
|
|
31
|
+
const init = { method, headers };
|
|
32
|
+
if (body !== void 0) {
|
|
33
|
+
headers["Content-Type"] = "application/json";
|
|
34
|
+
init.body = JSON.stringify(body);
|
|
35
|
+
}
|
|
36
|
+
return fetch(path, init);
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
listMembers: (orgId) => send(orgId, "GET", "/api/org/members").then((d) => rows(d, "members")),
|
|
40
|
+
updateMemberRole: (orgId, memberId, role) => {
|
|
41
|
+
segment(role, "role");
|
|
42
|
+
return send(orgId, "PUT", `/api/org/members/${segment(memberId, "memberId")}/role`, {
|
|
43
|
+
role
|
|
44
|
+
}).then(() => void 0);
|
|
45
|
+
},
|
|
46
|
+
removeMember: (orgId, memberId) => send(orgId, "DELETE", `/api/org/members/${segment(memberId, "memberId")}`).then(
|
|
47
|
+
() => void 0
|
|
48
|
+
),
|
|
49
|
+
inviteMember: (orgId, email, role, spaces) => {
|
|
50
|
+
if (typeof email !== "string" || email.trim() === "") {
|
|
51
|
+
throw new Error("email must be a non-empty string");
|
|
52
|
+
}
|
|
53
|
+
segment(role, "role");
|
|
54
|
+
const body = { email: email.trim(), role };
|
|
55
|
+
if (spaces) body.spaces = spaces;
|
|
56
|
+
return send(orgId, "POST", "/api/org/invite", body).then(
|
|
57
|
+
(d) => d
|
|
58
|
+
);
|
|
59
|
+
},
|
|
60
|
+
listInvitations: (orgId) => send(orgId, "GET", "/api/org/invitations").then((d) => rows(d, "invitations")),
|
|
61
|
+
revokeInvitation: (orgId, invitationId) => send(orgId, "DELETE", `/api/org/invitations/${segment(invitationId, "invitationId")}`).then(
|
|
62
|
+
() => void 0
|
|
63
|
+
),
|
|
64
|
+
resendInvitation: (orgId, invitationId) => send(
|
|
65
|
+
orgId,
|
|
66
|
+
"POST",
|
|
67
|
+
`/api/org/invitations/${segment(invitationId, "invitationId")}/resend`
|
|
68
|
+
).then(() => void 0)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
ORG_HEADER,
|
|
73
|
+
createTeamClient
|
|
74
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,72 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meetreeve/module-team",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Reeve.Team module (DEV-9998, C2 of epic DEV-9983): the CONFIG surface for an org's people - members table, role picker, remove member, invite dialog, pending invitations (revoke/resend) and seat count - plus a server-safe typed client over /api/org/*. Talks to the host through an injected fetch. Versioned in lockstep with the Python reeve-module-team package (both ship the same MAJOR.MINOR.PATCH).",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./client": {
|
|
16
|
+
"types": "./dist/client/index.d.ts",
|
|
17
|
+
"import": "./dist/client/index.mjs",
|
|
18
|
+
"require": "./dist/client/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./config": {
|
|
21
|
+
"types": "./dist/config/index.d.ts",
|
|
22
|
+
"import": "./dist/config/index.mjs",
|
|
23
|
+
"require": "./dist/config/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "rm -rf dist && tsup",
|
|
32
|
+
"dev": "tsup --watch",
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
35
|
+
"check:esm": "node ../../scripts/check-esm-import.mjs .",
|
|
36
|
+
"prepublishOnly": "npm run build"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"react": ">=18",
|
|
43
|
+
"react-dom": ">=18"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
47
|
+
"@testing-library/react": "^16.3.2",
|
|
48
|
+
"@testing-library/user-event": "^14.6.1",
|
|
49
|
+
"@types/node": "^20.19.43",
|
|
50
|
+
"@types/react": "^19",
|
|
51
|
+
"@types/react-dom": "^19",
|
|
52
|
+
"@vitejs/plugin-react": "^5.1.3",
|
|
53
|
+
"jsdom": "^28.0.0",
|
|
54
|
+
"react": "19.2.3",
|
|
55
|
+
"react-dom": "19.2.3",
|
|
56
|
+
"tsup": "^8.0.0",
|
|
57
|
+
"typescript": "5.9.3",
|
|
58
|
+
"vitest": "^4.0.18"
|
|
59
|
+
},
|
|
60
|
+
"keywords": [
|
|
61
|
+
"react",
|
|
62
|
+
"reeve",
|
|
63
|
+
"team",
|
|
64
|
+
"members",
|
|
65
|
+
"invitations"
|
|
66
|
+
],
|
|
67
|
+
"repository": {
|
|
68
|
+
"type": "git",
|
|
69
|
+
"url": "git+https://github.com/MindFortressInc/reeve-frontend.git",
|
|
70
|
+
"directory": "packages/module-team"
|
|
71
|
+
}
|
|
72
|
+
}
|