@miosa/sdk 1.2.5 → 1.2.6

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.
Files changed (92) hide show
  1. package/README.md +107 -59
  2. package/dist/index.d.ts +331 -202
  3. package/dist/index.js +572 -603
  4. package/dist/index.js.map +1 -1
  5. package/package.json +1 -2
  6. package/src/client.ts +0 -293
  7. package/src/errors.ts +0 -136
  8. package/src/http.test.ts +0 -374
  9. package/src/http.ts +0 -390
  10. package/src/index.ts +0 -569
  11. package/src/resources/admin.ts +0 -348
  12. package/src/resources/analytics.ts +0 -60
  13. package/src/resources/api-keys.ts +0 -119
  14. package/src/resources/audit-log.ts +0 -64
  15. package/src/resources/benchmarks.ts +0 -104
  16. package/src/resources/builder-sessions.ts +0 -75
  17. package/src/resources/channels.ts +0 -143
  18. package/src/resources/checkpoints.ts +0 -225
  19. package/src/resources/command-center.ts +0 -73
  20. package/src/resources/community.ts +0 -103
  21. package/src/resources/completions.ts +0 -104
  22. package/src/resources/computer-auto-stop.ts +0 -43
  23. package/src/resources/computer-env.ts +0 -76
  24. package/src/resources/computer-logs.ts +0 -50
  25. package/src/resources/computer-osa.ts +0 -76
  26. package/src/resources/computer-ports.ts +0 -91
  27. package/src/resources/computer-terminal.ts +0 -61
  28. package/src/resources/computer-volumes.ts +0 -64
  29. package/src/resources/computer.ts +0 -551
  30. package/src/resources/computers.ts +0 -75
  31. package/src/resources/credits.ts +0 -43
  32. package/src/resources/cron-jobs.ts +0 -191
  33. package/src/resources/custom_domains.ts +0 -123
  34. package/src/resources/dashboard.ts +0 -49
  35. package/src/resources/databases.ts +0 -218
  36. package/src/resources/deployments.test.ts +0 -197
  37. package/src/resources/deployments.ts +0 -1208
  38. package/src/resources/desktop.ts +0 -134
  39. package/src/resources/devices.test.ts +0 -92
  40. package/src/resources/devices.ts +0 -291
  41. package/src/resources/egress.test.ts +0 -318
  42. package/src/resources/egressAudit.ts +0 -245
  43. package/src/resources/egressNetwork.ts +0 -450
  44. package/src/resources/egressSecrets.ts +0 -577
  45. package/src/resources/email.ts +0 -212
  46. package/src/resources/embeddings.ts +0 -36
  47. package/src/resources/events.ts +0 -296
  48. package/src/resources/exec.ts +0 -319
  49. package/src/resources/external-keys.ts +0 -79
  50. package/src/resources/files.test.ts +0 -339
  51. package/src/resources/files.ts +0 -220
  52. package/src/resources/flat-custom-domains.ts +0 -127
  53. package/src/resources/functions.ts +0 -178
  54. package/src/resources/health-checks.ts +0 -165
  55. package/src/resources/integrations.ts +0 -183
  56. package/src/resources/mcp.ts +0 -70
  57. package/src/resources/models.ts +0 -45
  58. package/src/resources/network_policy.ts +0 -73
  59. package/src/resources/open-computers/agents.ts +0 -91
  60. package/src/resources/open-computers/apps.ts +0 -102
  61. package/src/resources/open-computers/clusters.ts +0 -88
  62. package/src/resources/open-computers/desktop.ts +0 -34
  63. package/src/resources/open-computers/files.ts +0 -97
  64. package/src/resources/open-computers/hosts.ts +0 -85
  65. package/src/resources/open-computers/index.ts +0 -98
  66. package/src/resources/open-computers/jobs.ts +0 -75
  67. package/src/resources/open-computers/open_computers.test.ts +0 -288
  68. package/src/resources/open-computers/secrets.ts +0 -115
  69. package/src/resources/open-computers/terminal.ts +0 -33
  70. package/src/resources/open-computers/tunnels.ts +0 -87
  71. package/src/resources/open-computers/types.ts +0 -343
  72. package/src/resources/open-computers/workspaces.ts +0 -135
  73. package/src/resources/org-invites.ts +0 -189
  74. package/src/resources/project-auth.ts +0 -142
  75. package/src/resources/project-integrations.ts +0 -133
  76. package/src/resources/provider-defaults.ts +0 -89
  77. package/src/resources/regions.ts +0 -94
  78. package/src/resources/sandbox-templates.ts +0 -195
  79. package/src/resources/sandboxes.live.test.ts +0 -92
  80. package/src/resources/sandboxes.test.ts +0 -655
  81. package/src/resources/sandboxes.ts +0 -1437
  82. package/src/resources/settings.ts +0 -143
  83. package/src/resources/snapshots-standalone.ts +0 -51
  84. package/src/resources/storage.ts +0 -221
  85. package/src/resources/tenant.ts +0 -66
  86. package/src/resources/usage.ts +0 -85
  87. package/src/resources/volumes.ts +0 -117
  88. package/src/resources/webhooks.ts +0 -239
  89. package/src/resources/workspace-invites.ts +0 -188
  90. package/src/resources/workspace-members.test.ts +0 -121
  91. package/src/resources/workspace-members.ts +0 -143
  92. package/src/types.ts +0 -463
@@ -1,239 +0,0 @@
1
- /**
2
- * Webhooks resource — tenant outgoing event delivery.
3
- */
4
-
5
- import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
6
-
7
- import type { HttpClient } from "../http.js";
8
-
9
- // ── Branded IDs ──────────────────────────────────────────────────────────────
10
-
11
- export type WebhookId = string & { readonly __brand: "WebhookId" };
12
- export type WebhookDeliveryId = string & {
13
- readonly __brand: "WebhookDeliveryId";
14
- };
15
-
16
- // ── Resource shapes ──────────────────────────────────────────────────────────
17
-
18
- export interface WebhookData {
19
- id: WebhookId;
20
- tenant_id: string;
21
- url: string;
22
- events: string[];
23
- state?: string;
24
- secret?: string | null;
25
- enabled?: boolean;
26
- created_at?: string;
27
- updated_at?: string;
28
- [key: string]: unknown;
29
- }
30
-
31
- export interface WebhookDeliveryData {
32
- id: WebhookDeliveryId;
33
- webhook_id: WebhookId;
34
- event?: string;
35
- state?: string;
36
- response_status?: number | null;
37
- attempt_count?: number;
38
- next_retry_at?: string | null;
39
- delivered_at?: string | null;
40
- created_at?: string;
41
- [key: string]: unknown;
42
- }
43
-
44
- // ── Request payloads ─────────────────────────────────────────────────────────
45
-
46
- export interface WebhookListParams {
47
- limit?: number;
48
- cursor?: string;
49
- [key: string]: string | number | boolean | undefined;
50
- }
51
-
52
- export interface WebhookCreateParams {
53
- url: string;
54
- events: string[];
55
- secret?: string;
56
- enabled?: boolean;
57
- idempotencyKey?: string;
58
- [key: string]: unknown;
59
- }
60
-
61
- export interface WebhookUpdateParams {
62
- url?: string;
63
- events?: string[];
64
- secret?: string;
65
- enabled?: boolean;
66
- [key: string]: unknown;
67
- }
68
-
69
- export interface WebhookSignatureVerifyOptions {
70
- /** Maximum age for the webhook timestamp in seconds. Defaults to 300. */
71
- toleranceSeconds?: number;
72
- }
73
-
74
- // ── Helpers ───────────────────────────────────────────────────────────────────
75
-
76
- function unwrap<T>(payload: unknown): T {
77
- if (payload && typeof payload === "object" && "data" in (payload as object)) {
78
- return (payload as { data: T }).data;
79
- }
80
- return payload as T;
81
- }
82
-
83
- function listItems<T>(
84
- payload: unknown,
85
- candidateKeys: string[] = ["data", "webhooks", "deliveries", "items"],
86
- ): T[] {
87
- if (Array.isArray(payload)) return payload;
88
- if (!payload || typeof payload !== "object") return [];
89
- const p = payload as Record<string, unknown>;
90
- if (Array.isArray(p.data)) return p.data as T[];
91
- for (const key of candidateKeys) {
92
- if (Array.isArray(p[key])) return p[key] as T[];
93
- }
94
- return [];
95
- }
96
-
97
- function stripUndefined(
98
- input: Record<string, unknown>,
99
- ): Record<string, unknown> {
100
- return Object.fromEntries(
101
- Object.entries(input).filter(([, v]) => v !== undefined),
102
- );
103
- }
104
-
105
- function idempotencyKey(key?: string): string {
106
- return key ?? randomUUID();
107
- }
108
-
109
- function parseSignatureHeader(header: string): {
110
- timestamp: number;
111
- signatures: string[];
112
- } | null {
113
- const parts = header.split(",").map((part) => part.trim());
114
- let timestamp: number | null = null;
115
- const signatures: string[] = [];
116
-
117
- for (const part of parts) {
118
- const [key, value] = part.split("=", 2);
119
- if (!key || !value) continue;
120
- if (key === "t") {
121
- const parsed = Number(value);
122
- if (Number.isFinite(parsed)) timestamp = parsed;
123
- } else if (key === "v1") {
124
- signatures.push(value);
125
- }
126
- }
127
-
128
- if (timestamp == null || signatures.length === 0) return null;
129
- return { timestamp, signatures };
130
- }
131
-
132
- /**
133
- * Verify a MIOSA webhook signature header.
134
- *
135
- * Header format: `t=<unix_seconds>,v1=<hex_hmac_sha256>`.
136
- */
137
- export function verifySignature(
138
- body: string | Buffer | Uint8Array,
139
- header: string,
140
- secret: string,
141
- options: WebhookSignatureVerifyOptions = {},
142
- ): boolean {
143
- const parsed = parseSignatureHeader(header);
144
- if (!parsed) return false;
145
-
146
- const toleranceSeconds = options.toleranceSeconds ?? 300;
147
- const ageSeconds = Math.abs(Math.floor(Date.now() / 1000) - parsed.timestamp);
148
- if (ageSeconds > toleranceSeconds) {
149
- throw new Error("Webhook signature timestamp is too old");
150
- }
151
-
152
- const bodyBuffer = Buffer.isBuffer(body) ? body : Buffer.from(body);
153
- const signedPayload = Buffer.concat([
154
- Buffer.from(`${parsed.timestamp}.`),
155
- bodyBuffer,
156
- ]);
157
- const expected = createHmac("sha256", secret)
158
- .update(signedPayload)
159
- .digest("hex");
160
- const expectedBuffer = Buffer.from(expected, "hex");
161
-
162
- return parsed.signatures.some((signature) => {
163
- const actualBuffer = Buffer.from(signature, "hex");
164
- if (actualBuffer.length !== expectedBuffer.length) return false;
165
- return timingSafeEqual(actualBuffer, expectedBuffer);
166
- });
167
- }
168
-
169
- // ── Main resource ─────────────────────────────────────────────────────────────
170
-
171
- export class Webhooks {
172
- static verifySignature = verifySignature;
173
- static verify_signature = verifySignature;
174
-
175
- constructor(private readonly http: HttpClient) {}
176
-
177
- async list(params: WebhookListParams = {}): Promise<WebhookData[]> {
178
- const query = stripUndefined({ ...params }) as Record<
179
- string,
180
- string | number | boolean | undefined
181
- >;
182
- const data = await this.http.get<unknown>("/webhooks", query);
183
- return listItems<WebhookData>(data);
184
- }
185
-
186
- async get(webhookId: string): Promise<WebhookData> {
187
- const data = await this.http.get<unknown>(`/webhooks/${webhookId}`);
188
- return unwrap<WebhookData>(data);
189
- }
190
-
191
- async create(params: WebhookCreateParams): Promise<WebhookData> {
192
- const { idempotencyKey: ikey, ...rest } = params;
193
- const body = stripUndefined(rest as Record<string, unknown>);
194
- const data = await this.http.request<unknown>("/webhooks", {
195
- method: "POST",
196
- body,
197
- headers: { "Idempotency-Key": idempotencyKey(ikey) },
198
- });
199
- return unwrap<WebhookData>(data);
200
- }
201
-
202
- async update(
203
- webhookId: string,
204
- params: WebhookUpdateParams,
205
- ): Promise<WebhookData> {
206
- const body = stripUndefined(params as Record<string, unknown>);
207
- const data = await this.http.patch<unknown>(`/webhooks/${webhookId}`, body);
208
- return unwrap<WebhookData>(data);
209
- }
210
-
211
- async delete(webhookId: string): Promise<void> {
212
- await this.http.delete<unknown>(`/webhooks/${webhookId}`);
213
- }
214
-
215
- async test(
216
- webhookId: string,
217
- opts: { idempotencyKey?: string } = {},
218
- ): Promise<Record<string, unknown>> {
219
- const data = await this.http.request<unknown>(
220
- `/webhooks/${webhookId}/test`,
221
- {
222
- method: "POST",
223
- headers: { "Idempotency-Key": idempotencyKey(opts.idempotencyKey) },
224
- },
225
- );
226
- return unwrap<Record<string, unknown>>(data);
227
- }
228
-
229
- async deliveries(webhookId: string): Promise<WebhookDeliveryData[]> {
230
- const data = await this.http.get<unknown>(
231
- `/webhooks/${webhookId}/deliveries`,
232
- );
233
- return listItems<WebhookDeliveryData>(data, [
234
- "data",
235
- "deliveries",
236
- "items",
237
- ]);
238
- }
239
- }
@@ -1,188 +0,0 @@
1
- /**
2
- * Workspace Invites — email invite flow for workspace access.
3
- *
4
- * Sending an invite to an email that already belongs to a tenant member
5
- * short-circuits to directly adding that user (returns `type: "added"`).
6
- * Accepting a workspace invite for an unknown email auto-creates both a
7
- * `tenant_members` and a `workspace_members` row atomically.
8
- *
9
- * Public endpoints (no auth):
10
- * GET /workspace-invites/:token
11
- *
12
- * Authenticated endpoints:
13
- * POST /workspaces/:id/invites
14
- * GET /workspaces/:id/invites
15
- * DELETE /workspaces/:id/invites/:invite_id
16
- * POST /workspace-invites/:token/accept
17
- */
18
-
19
- import type { HttpClient } from "../http.js";
20
- import type {
21
- WorkspaceMemberRecord,
22
- WorkspaceRole,
23
- } from "./workspace-members.js";
24
-
25
- // ── Resource shapes ──────────────────────────────────────────────────────────
26
-
27
- export interface WorkspaceInvite {
28
- id: string;
29
- workspace_id: string;
30
- tenant_id: string;
31
- email: string;
32
- role: WorkspaceRole;
33
- invited_by: string | null;
34
- expires_at: string;
35
- accepted_at: string | null;
36
- inserted_at: string;
37
- }
38
-
39
- export interface WorkspaceInvitePreview {
40
- workspace_name: string;
41
- tenant_name: string;
42
- role: WorkspaceRole;
43
- email: string;
44
- expires_at: string;
45
- expired: boolean;
46
- revoked: boolean;
47
- accepted: boolean;
48
- }
49
-
50
- // ── Request payloads ─────────────────────────────────────────────────────────
51
-
52
- export interface CreateWorkspaceInviteParams {
53
- email: string;
54
- role?: WorkspaceRole;
55
- }
56
-
57
- // ── Response shapes ───────────────────────────────────────────────────────────
58
-
59
- /** Returned when the email was unknown — an invite was created. */
60
- export interface WorkspaceInviteCreatedResponse {
61
- data: WorkspaceInvite;
62
- type: "invited";
63
- }
64
-
65
- /** Returned when the email already had a tenant_members row — added directly. */
66
- export interface WorkspaceMemberAddedResponse {
67
- data: WorkspaceMemberRecord;
68
- type: "added";
69
- }
70
-
71
- export type CreateWorkspaceInviteResponse =
72
- | WorkspaceInviteCreatedResponse
73
- | WorkspaceMemberAddedResponse;
74
-
75
- export interface WorkspaceInviteListResponse {
76
- data: WorkspaceInvite[];
77
- total: number;
78
- }
79
-
80
- export interface WorkspaceInviteRevokeResponse {
81
- invite_id: string;
82
- revoked: boolean;
83
- }
84
-
85
- export interface WorkspaceInvitePreviewResponse {
86
- data: WorkspaceInvitePreview;
87
- }
88
-
89
- export interface AcceptWorkspaceInviteResponse {
90
- accepted: boolean;
91
- workspace_id: string;
92
- tenant_id: string;
93
- role: WorkspaceRole;
94
- }
95
-
96
- // ── Main resource ─────────────────────────────────────────────────────────────
97
-
98
- export class WorkspaceInvites {
99
- constructor(private readonly http: HttpClient) {}
100
-
101
- /**
102
- * Create a workspace invite or add a member directly.
103
- *
104
- * If `email` already maps to a tenant member the user is added directly and
105
- * `type === "added"` is returned with a `WorkspaceMemberRecord`. Otherwise
106
- * an invite row is created and `type === "invited"` is returned.
107
- *
108
- * `POST /workspaces/:id/invites`
109
- */
110
- async create(
111
- workspaceId: string,
112
- params: CreateWorkspaceInviteParams,
113
- ): Promise<CreateWorkspaceInviteResponse> {
114
- return this.http.post<CreateWorkspaceInviteResponse>(
115
- `/workspaces/${workspaceId}/invites`,
116
- params,
117
- );
118
- }
119
-
120
- /**
121
- * List all pending (non-expired, non-accepted, non-revoked) workspace invites.
122
- *
123
- * `GET /workspaces/:id/invites`
124
- */
125
- async list(workspaceId: string): Promise<WorkspaceInvite[]> {
126
- const res = await this.http.get<WorkspaceInviteListResponse>(
127
- `/workspaces/${workspaceId}/invites`,
128
- );
129
- return res.data ?? [];
130
- }
131
-
132
- /**
133
- * Revoke a pending workspace invite.
134
- *
135
- * Already-revoked invites are idempotent (returns `revoked: true`). An invite
136
- * that was legitimately accepted throws `409 ALREADY_ACCEPTED`.
137
- *
138
- * `DELETE /workspaces/:id/invites/:invite_id`
139
- */
140
- async revoke(
141
- workspaceId: string,
142
- inviteId: string,
143
- ): Promise<WorkspaceInviteRevokeResponse> {
144
- return this.http.delete<WorkspaceInviteRevokeResponse>(
145
- `/workspaces/${workspaceId}/invites/${inviteId}`,
146
- );
147
- }
148
-
149
- /**
150
- * Preview a workspace invite by token (no auth required).
151
- *
152
- * Use this to render the invite landing page before prompting the user to
153
- * log in or sign up. Returns `null` when the token is unknown or revoked.
154
- *
155
- * `GET /workspace-invites/:token`
156
- */
157
- async preview(token: string): Promise<WorkspaceInvitePreview | null> {
158
- try {
159
- const res = await this.http.get<WorkspaceInvitePreviewResponse>(
160
- `/workspace-invites/${token}`,
161
- );
162
- return res.data ?? null;
163
- } catch {
164
- return null;
165
- }
166
- }
167
-
168
- /**
169
- * Accept a workspace invite on behalf of the authenticated user.
170
- *
171
- * The caller's JWT email must match the invite email (case-insensitive).
172
- *
173
- * Error codes:
174
- * - `INVALID_TOKEN` (404) — token not found.
175
- * - `EXPIRED` (410) — invite TTL elapsed.
176
- * - `REVOKED` (409) — invite was revoked.
177
- * - `ALREADY_ACCEPTED` (409) — already used.
178
- * - `EMAIL_MISMATCH` (422) — JWT email differs from invite email.
179
- *
180
- * `POST /workspace-invites/:token/accept`
181
- */
182
- async accept(token: string): Promise<AcceptWorkspaceInviteResponse> {
183
- return this.http.post<AcceptWorkspaceInviteResponse>(
184
- `/workspace-invites/${token}/accept`,
185
- {},
186
- );
187
- }
188
- }
@@ -1,121 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
- import { HttpClient } from "../http.js";
3
- import { WorkspaceMembers } from "./workspace-members.js";
4
-
5
- const mockGet = vi.fn();
6
- const mockPost = vi.fn();
7
- const mockPatch = vi.fn();
8
- const mockDelete = vi.fn();
9
- const mockRequest = vi.fn();
10
-
11
- function makeHttp(): HttpClient {
12
- const http = {} as HttpClient;
13
- http.get = mockGet;
14
- http.post = mockPost;
15
- http.patch = mockPatch;
16
- http.delete = mockDelete;
17
- http.request = mockRequest;
18
- return http;
19
- }
20
-
21
- const MEMBER = {
22
- user_id: "usr_abc",
23
- email: "alice@example.com",
24
- name: "Alice",
25
- avatar_url: null,
26
- role: "member" as const,
27
- joined_at: "2026-05-01T10:00:00Z",
28
- added_by: null,
29
- };
30
-
31
- beforeEach(() => {
32
- vi.clearAllMocks();
33
- });
34
-
35
- describe("WorkspaceMembers", () => {
36
- describe("list()", () => {
37
- it("calls GET /workspaces/:id/members and returns array", async () => {
38
- mockGet.mockResolvedValue({ data: [MEMBER] });
39
- const resource = new WorkspaceMembers(makeHttp());
40
-
41
- const result = await resource.list("ws-uuid");
42
-
43
- expect(mockGet).toHaveBeenCalledWith("/workspaces/ws-uuid/members");
44
- expect(result).toHaveLength(1);
45
- expect(result[0].user_id).toBe("usr_abc");
46
- expect(result[0].email).toBe("alice@example.com");
47
- expect(result[0].role).toBe("member");
48
- });
49
-
50
- it("returns empty array when data is empty", async () => {
51
- mockGet.mockResolvedValue({ data: [] });
52
- const resource = new WorkspaceMembers(makeHttp());
53
-
54
- const result = await resource.list("ws-uuid");
55
- expect(result).toEqual([]);
56
- });
57
- });
58
-
59
- describe("add()", () => {
60
- it("calls POST /workspaces/:id/members with userId and role", async () => {
61
- const record = {
62
- user_id: "usr_def",
63
- workspace_id: "ws-uuid",
64
- role: "member",
65
- joined_at: "2026-05-22T09:00:00Z",
66
- added_by: "usr_abc",
67
- };
68
- mockPost.mockResolvedValue({ data: record });
69
- const resource = new WorkspaceMembers(makeHttp());
70
-
71
- const result = await resource.add("ws-uuid", {
72
- user_id: "usr_def",
73
- role: "member",
74
- });
75
-
76
- expect(mockPost).toHaveBeenCalledWith(
77
- "/workspaces/ws-uuid/members",
78
- expect.objectContaining({ user_id: "usr_def", role: "member" }),
79
- );
80
- expect(result.user_id).toBe("usr_def");
81
- expect(result.role).toBe("member");
82
- });
83
- });
84
-
85
- describe("updateRole()", () => {
86
- it("calls PATCH /workspaces/:id/members/:userId with new role", async () => {
87
- const updated = {
88
- user_id: "usr_def",
89
- workspace_id: "ws-uuid",
90
- role: "admin",
91
- joined_at: "2026-05-22T09:00:00Z",
92
- added_by: "usr_abc",
93
- };
94
- mockPatch.mockResolvedValue({ data: updated });
95
- const resource = new WorkspaceMembers(makeHttp());
96
-
97
- const result = await resource.updateRole("ws-uuid", "usr_def", {
98
- role: "admin",
99
- });
100
-
101
- expect(mockPatch).toHaveBeenCalledWith(
102
- "/workspaces/ws-uuid/members/usr_def",
103
- { role: "admin" },
104
- );
105
- expect(result.role).toBe("admin");
106
- });
107
- });
108
-
109
- describe("remove()", () => {
110
- it("calls DELETE /workspaces/:id/members/:userId", async () => {
111
- mockDelete.mockResolvedValue({ deleted: true });
112
- const resource = new WorkspaceMembers(makeHttp());
113
-
114
- await resource.remove("ws-uuid", "usr_def");
115
-
116
- expect(mockDelete).toHaveBeenCalledWith(
117
- "/workspaces/ws-uuid/members/usr_def",
118
- );
119
- });
120
- });
121
- });
@@ -1,143 +0,0 @@
1
- /**
2
- * Workspace Members — per-workspace user roster.
3
- *
4
- * Endpoints:
5
- * GET /workspaces/:id/members
6
- * POST /workspaces/:id/members
7
- * PATCH /workspaces/:id/members/:user_id
8
- * DELETE /workspaces/:id/members/:user_id
9
- */
10
-
11
- import type { HttpClient } from "../http.js";
12
-
13
- // ── Resource shapes ──────────────────────────────────────────────────────────
14
-
15
- /** Role a user can hold within a workspace. */
16
- export type WorkspaceRole = "owner" | "admin" | "member" | "viewer";
17
-
18
- /**
19
- * Workspace member as returned by `list` — includes denormalised user fields
20
- * for display.
21
- */
22
- export interface WorkspaceMember {
23
- user_id: string;
24
- email: string | null;
25
- name: string | null;
26
- avatar_url: string | null;
27
- role: WorkspaceRole;
28
- joined_at: string | null;
29
- added_by: string | null;
30
- }
31
-
32
- /** Raw workspace_members row returned after add/update operations. */
33
- export interface WorkspaceMemberRecord {
34
- user_id: string;
35
- workspace_id: string;
36
- role: WorkspaceRole;
37
- joined_at: string | null;
38
- added_by: string | null;
39
- }
40
-
41
- // ── Request payloads ─────────────────────────────────────────────────────────
42
-
43
- export interface AddWorkspaceMemberParams {
44
- /** UUID of the tenant user to add. Must already be an org member. */
45
- user_id: string;
46
- /** Role to assign; defaults to `"member"`. */
47
- role?: WorkspaceRole;
48
- }
49
-
50
- export interface UpdateWorkspaceMemberRoleParams {
51
- role: WorkspaceRole;
52
- }
53
-
54
- // ── Response envelopes ───────────────────────────────────────────────────────
55
-
56
- export interface WorkspaceMemberListResponse {
57
- data: WorkspaceMember[];
58
- }
59
-
60
- export interface WorkspaceMemberRecordResponse {
61
- data: WorkspaceMemberRecord;
62
- }
63
-
64
- export interface WorkspaceMemberDeleteResponse {
65
- deleted: boolean;
66
- }
67
-
68
- // ── Main resource ─────────────────────────────────────────────────────────────
69
-
70
- export class WorkspaceMembers {
71
- constructor(private readonly http: HttpClient) {}
72
-
73
- /**
74
- * List all members of a workspace.
75
- *
76
- * `GET /workspaces/:id/members`
77
- */
78
- async list(workspaceId: string): Promise<WorkspaceMember[]> {
79
- const res = await this.http.get<WorkspaceMemberListResponse>(
80
- `/workspaces/${workspaceId}/members`,
81
- );
82
- return res.data ?? (res as unknown as WorkspaceMember[]);
83
- }
84
-
85
- /**
86
- * Add an existing tenant user to a workspace.
87
- *
88
- * The `user_id` must already hold a `tenant_members` row for the parent org.
89
- * Use {@link WorkspaceInvites.create} to invite someone who is not yet an org
90
- * member.
91
- *
92
- * `POST /workspaces/:id/members`
93
- *
94
- * @throws `MiosaError` with code `NOT_TENANT_MEMBER` if the user is not an
95
- * org member.
96
- */
97
- async add(
98
- workspaceId: string,
99
- params: AddWorkspaceMemberParams,
100
- ): Promise<WorkspaceMemberRecord> {
101
- const res = await this.http.post<WorkspaceMemberRecordResponse>(
102
- `/workspaces/${workspaceId}/members`,
103
- params,
104
- );
105
- return res.data ?? (res as unknown as WorkspaceMemberRecord);
106
- }
107
-
108
- /**
109
- * Change a workspace member's role.
110
- *
111
- * `PATCH /workspaces/:id/members/:user_id`
112
- */
113
- async updateRole(
114
- workspaceId: string,
115
- userId: string,
116
- params: UpdateWorkspaceMemberRoleParams,
117
- ): Promise<WorkspaceMemberRecord> {
118
- const res = await this.http.patch<WorkspaceMemberRecordResponse>(
119
- `/workspaces/${workspaceId}/members/${userId}`,
120
- params,
121
- );
122
- return res.data ?? (res as unknown as WorkspaceMemberRecord);
123
- }
124
-
125
- /**
126
- * Remove a user from a workspace.
127
- *
128
- * The last `owner` of a workspace cannot be removed. Promote another member
129
- * to `owner` first using {@link updateRole}.
130
- *
131
- * `DELETE /workspaces/:id/members/:user_id`
132
- *
133
- * @throws `MiosaError` with code `LAST_OWNER` if the target is the sole owner.
134
- */
135
- async remove(
136
- workspaceId: string,
137
- userId: string,
138
- ): Promise<WorkspaceMemberDeleteResponse> {
139
- return this.http.delete<WorkspaceMemberDeleteResponse>(
140
- `/workspaces/${workspaceId}/members/${userId}`,
141
- );
142
- }
143
- }