orizu 0.5.45 → 0.5.46
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/account-directory.d.ts.map +1 -1
- package/dist/account-directory.js +4 -11
- package/dist/team-member-add-workflow.d.ts +15 -0
- package/dist/team-member-add-workflow.d.ts.map +1 -0
- package/dist/team-member-add-workflow.js +111 -0
- package/package.json +1 -1
- package/src/account-directory.ts +4 -11
- package/src/team-member-add-workflow.ts +113 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account-directory.d.ts","sourceRoot":"","sources":["../src/account-directory.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,2BAA2B,EAAe,MAAM,WAAW,CAAA;AAEzE,UAAU,IAAI;IACZ,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AAED,UAAU,OAAO;IACf,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,UAAU,IAAI;IACZ,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,UAAU,UAAU;IAClB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,iBAAiB,EAAE,MAAM,CAAA;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B;AAED,UAAU,cAAc;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B;AAED,UAAU,gBAAgB;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAUD,UAAU,iBAAiB;IACzB,IAAI,EAAE;QACJ,EAAE,EAAE,MAAM,CAAA;QACV,KAAK,EAAE,MAAM,CAAA;QACb,MAAM,EAAE,MAAM,CAAA;QACd,SAAS,EAAE,MAAM,CAAA;QACjB,QAAQ,EAAE,MAAM,CAAA;QAChB,QAAQ,EAAE,MAAM,CAAA;QAChB,WAAW,EAAE,MAAM,CAAA;QACnB,WAAW,EAAE,MAAM,CAAA;QACnB,eAAe,EAAE,MAAM,CAAA;QACvB,yBAAyB,EAAE,MAAM,CAAA;QACjC,wBAAwB,EAAE,MAAM,CAAA;QAChC,MAAM,EAAE;YACN,SAAS,EAAE,MAAM,CAAA;YACjB,UAAU,EAAE,MAAM,CAAA;YAClB,OAAO,EAAE,MAAM,CAAA;YACf,OAAO,EAAE,MAAM,CAAA;SAChB,CAAA;QACD,kBAAkB,EAAE,MAAM,CAAA;QAC1B,SAAS,EAAE,KAAK,CAAC;YACf,UAAU,EAAE,MAAM,CAAA;YAClB,KAAK,EAAE,MAAM,CAAA;YACb,SAAS,EAAE,MAAM,CAAA;YACjB,UAAU,EAAE,MAAM,CAAA;YAClB,OAAO,EAAE,MAAM,CAAA;YACf,OAAO,EAAE,MAAM,CAAA;YACf,KAAK,EAAE,MAAM,CAAA;SACd,CAAC,CAAA;KACH,CAAA;CACF;AAED,QAAA,MAAM,yBAAyB;;;;;;;;;;;;;CAarB,CAAA;AA2CV,KAAK,oBAAoB,GAAG,2BAA2B,CAAC,OAAO,CAAC,CAAA;AAChE,iBAAe,UAAU,CAAC,OAAO,GAAE,oBAAkC,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAOtF;AAED,iBAAe,aAAa,CAC1B,QAAQ,CAAC,EAAE,MAAM,EACjB,OAAO,GAAE,oBAAkC,GAC1C,OAAO,CAAC,OAAO,EAAE,CAAC,CASpB;AAED,iBAAe,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,SAAW,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAY9E;AAED,iBAAe,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAW,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAUlF;AAED,iBAAe,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAW,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAU1F;AAYD,iBAAe,kBAAkB,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,oBAAkC,GAC1C,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,iBAAe,qBAAqB,CAClC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,oBAAkC,GAC1C,OAAO,CAAC,OAAO,CAAC,CA6BlB;AAED,iBAAe,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAmElF;AAED,iBAAe,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAG5E;AAED,iBAAe,yBAAyB,IAAI,OAAO,CAAC,MAAM,CAAC,CAwB1D;AAED,iBAAe,wBAAwB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAkB9G;AAED,iBAAe,0BAA0B,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmB9F;AA2FD,iBAAS,sBAAsB,CAAC,IAAI,EAAE,iBAAiB,QAiBtD;AAED,iBAAe,SAAS,kBAOvB;AAiBD,iBAAe,UAAU,kBAsBxB;AAED,iBAAe,YAAY,kBAS1B;AAED,iBAAe,aAAa,kBAyB3B;AAED,iBAAe,eAAe,kBAQ7B;AAED,iBAAe,aAAa,
|
|
1
|
+
{"version":3,"file":"account-directory.d.ts","sourceRoot":"","sources":["../src/account-directory.ts"],"names":[],"mappings":"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"}
|
|
@@ -357,20 +357,13 @@ async function addTeamMember() {
|
|
|
357
357
|
if (!email) {
|
|
358
358
|
throw new Error('Usage: orizu teams members add --email <email> [--team <teamSlug>]');
|
|
359
359
|
}
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
headers: { 'Content-Type': 'application/json' },
|
|
363
|
-
body: JSON.stringify({ email }),
|
|
364
|
-
});
|
|
365
|
-
if (!response.ok) {
|
|
366
|
-
throw new Error(`Failed to add team member: ${await response.text()}`);
|
|
367
|
-
}
|
|
368
|
-
const data = await parseJsonResponse(response, 'Team member add');
|
|
360
|
+
const { addTeamMemberWithDeadline } = await import('./team-member-add-workflow.js');
|
|
361
|
+
const member = await addTeamMemberWithDeadline(teamSlug, email, authedFetch);
|
|
369
362
|
if (hasJsonFlag()) {
|
|
370
|
-
printJson({ member
|
|
363
|
+
printJson({ member });
|
|
371
364
|
return;
|
|
372
365
|
}
|
|
373
|
-
printLine(`Added team member ${sanitizeTerminalText(
|
|
366
|
+
printLine(`Added team member ${sanitizeTerminalText(member.email)} (${sanitizeTerminalText(member.id)})`);
|
|
374
367
|
}
|
|
375
368
|
async function removeTeamMember() {
|
|
376
369
|
const teamSlug = await resolveTeamSlug(getArg('--team'));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Schema } from 'effect';
|
|
2
|
+
declare const AddReply: Schema.Struct<{
|
|
3
|
+
readonly member: Schema.Struct<{
|
|
4
|
+
readonly id: Schema.NonEmptyString;
|
|
5
|
+
readonly email: Schema.String;
|
|
6
|
+
}>;
|
|
7
|
+
}>;
|
|
8
|
+
/** The member as the server sent it; `--json` prints every field it has. */
|
|
9
|
+
export type AddedMember = typeof AddReply.Type['member'] & Readonly<Record<string, unknown>>;
|
|
10
|
+
/** Sends one request; calls `onRequestStart` just before anything goes out. */
|
|
11
|
+
export type TeamMemberAddRequest = (path: string, init: RequestInit, onRequestStart?: () => void) => Promise<Response>;
|
|
12
|
+
/** `orizu teams members add`: the invited member, or an error saying whether the invite may be saved. */
|
|
13
|
+
export declare function addTeamMemberWithDeadline(teamSlug: string, email: string, request: TeamMemberAddRequest): Promise<AddedMember>;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=team-member-add-workflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team-member-add-workflow.d.ts","sourceRoot":"","sources":["../src/team-member-add-workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+B,MAAM,EAAE,MAAM,QAAQ,CAAA;AAmB5D,QAAA,MAAM,QAAQ;;;;;EAKZ,CAAA;AAEF,4EAA4E;AAC5E,MAAM,MAAM,WAAW,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAE5F,+EAA+E;AAC/E,MAAM,MAAM,oBAAoB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,cAAc,CAAC,EAAE,MAAM,IAAI,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;AA2EtH,yGAAyG;AACzG,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,CAAC,CAMpI"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Cause, Effect, Exit, Result, Schema } from 'effect';
|
|
2
|
+
import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js';
|
|
3
|
+
import { extractErrorMessage } from './error-response.js';
|
|
4
|
+
import { sanitizeTerminalText } from './json-response.js';
|
|
5
|
+
import { appErrorOf, unreachedCode } from './request-outcome.js';
|
|
6
|
+
// Loaded only through `await import(...)`, like the dataset workflows.
|
|
7
|
+
const FAILED = 'Failed to add team member';
|
|
8
|
+
// What the route says when it cannot tell whether the invite was saved (ORI-2383 slice A).
|
|
9
|
+
const SERVER_MAY_BE_SAVED = 'Invite may already be saved. Refresh the member list before inviting again.';
|
|
10
|
+
// The route saves the address trimmed and lowercased, so the check names that address.
|
|
11
|
+
const mayBeInvited = (teamSlug, email) => {
|
|
12
|
+
const saved = sanitizeTerminalText(email.trim().toLowerCase());
|
|
13
|
+
return `${saved} may already be invited: run \`orizu teams members list --team ${teamSlug}\` and look for ${saved}. If they are listed, they were invited; don't invite them again.`;
|
|
14
|
+
};
|
|
15
|
+
const AddReply = Schema.Struct({
|
|
16
|
+
member: Schema.Struct({
|
|
17
|
+
id: Schema.NonEmptyString,
|
|
18
|
+
email: Schema.String,
|
|
19
|
+
}),
|
|
20
|
+
});
|
|
21
|
+
// The invite may be saved when the server says so, when the reply was lost,
|
|
22
|
+
// late or unreadable after the request was sent, when a 5xx did not come from
|
|
23
|
+
// the app (a gateway giving up while the app kept working), or when the reply
|
|
24
|
+
// has no member. Any other error reply is a clear refusal.
|
|
25
|
+
class TeamMemberAddFailure extends Error {
|
|
26
|
+
mayBeSaved;
|
|
27
|
+
_tag = 'TeamMemberAddFailure';
|
|
28
|
+
constructor(message, mayBeSaved) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.mayBeSaved = mayBeSaved;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// One request, never retried: a retry after a saved invite is refused as
|
|
34
|
+
// "already a member", which hides that the first one worked.
|
|
35
|
+
function memberAdd(teamSlug, email, request, timeoutMs) {
|
|
36
|
+
return Effect.suspend(() => {
|
|
37
|
+
let sent = false;
|
|
38
|
+
let timedOut = false;
|
|
39
|
+
return Effect.tryPromise({
|
|
40
|
+
try: async (signal) => {
|
|
41
|
+
const controller = new AbortController();
|
|
42
|
+
const abort = () => controller.abort();
|
|
43
|
+
const timer = setTimeout(() => { timedOut = true; controller.abort(); }, timeoutMs);
|
|
44
|
+
if (signal.aborted)
|
|
45
|
+
abort();
|
|
46
|
+
else
|
|
47
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
48
|
+
try {
|
|
49
|
+
const response = await request(`/api/cli/teams/${encodeURIComponent(teamSlug)}/members`, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: { 'Content-Type': 'application/json' },
|
|
52
|
+
body: JSON.stringify({ email }),
|
|
53
|
+
signal: controller.signal,
|
|
54
|
+
}, () => { sent = true; });
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
const appError = await appErrorOf(response.clone());
|
|
57
|
+
const detail = await extractErrorMessage(response);
|
|
58
|
+
if (appError === SERVER_MAY_BE_SAVED)
|
|
59
|
+
throw new TeamMemberAddFailure(appError, true);
|
|
60
|
+
if (response.status < 400 || (response.status >= 500 && appError === null)) {
|
|
61
|
+
throw new TeamMemberAddFailure(`Member add got no clear answer (status ${response.status}).`, true);
|
|
62
|
+
}
|
|
63
|
+
// The route's own failures already say what failed.
|
|
64
|
+
throw new TeamMemberAddFailure(detail.startsWith('Failed to ') ? detail : `${FAILED}: ${detail}`, false);
|
|
65
|
+
}
|
|
66
|
+
// Reading the body can still fail or stall; that stays under the deadline.
|
|
67
|
+
const body = await response.text();
|
|
68
|
+
let data;
|
|
69
|
+
try {
|
|
70
|
+
data = JSON.parse(body);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
data = undefined;
|
|
74
|
+
}
|
|
75
|
+
if (Result.isFailure(Schema.decodeUnknownResult(AddReply)(data))) {
|
|
76
|
+
throw new TeamMemberAddFailure(`Member add returned an unexpected response (status ${response.status}).`, true);
|
|
77
|
+
}
|
|
78
|
+
// Decoding keeps only the named fields; the reply's member is returned whole.
|
|
79
|
+
return data.member;
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
signal.removeEventListener('abort', abort);
|
|
84
|
+
controller.abort();
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
catch: cause => {
|
|
88
|
+
if (cause instanceof TeamMemberAddFailure)
|
|
89
|
+
return cause;
|
|
90
|
+
if (timedOut)
|
|
91
|
+
return new TeamMemberAddFailure('Member add exceeded its deadline.', sent);
|
|
92
|
+
const unreached = unreachedCode(cause);
|
|
93
|
+
if (unreached)
|
|
94
|
+
return new TeamMemberAddFailure(`${FAILED}: could not reach the server (${unreached}).`, false);
|
|
95
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
96
|
+
if (!sent || message.startsWith('ORIZU_AUTH_CONTEXT_CHANGED:'))
|
|
97
|
+
return new TeamMemberAddFailure(message, false);
|
|
98
|
+
// Also reached when a sign-in refresh fails after a 401: still told to
|
|
99
|
+
// check, so say only that the invite did not finish.
|
|
100
|
+
return new TeamMemberAddFailure(`Member add did not finish: ${message}`, true);
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/** `orizu teams members add`: the invited member, or an error saying whether the invite may be saved. */
|
|
106
|
+
export async function addTeamMemberWithDeadline(teamSlug, email, request) {
|
|
107
|
+
const exit = await Effect.runPromiseExit(memberAdd(teamSlug, email, request, datasetTransferTimeoutMs()).pipe(Effect.mapError(failure => new Error(failure.mayBeSaved ? `${failure.message}\n${mayBeInvited(teamSlug, email)}` : failure.message))));
|
|
108
|
+
if (Exit.isSuccess(exit))
|
|
109
|
+
return exit.value;
|
|
110
|
+
throw Cause.squash(exit.cause);
|
|
111
|
+
}
|
package/package.json
CHANGED
package/src/account-directory.ts
CHANGED
|
@@ -634,20 +634,13 @@ async function addTeamMember() {
|
|
|
634
634
|
throw new Error('Usage: orizu teams members add --email <email> [--team <teamSlug>]')
|
|
635
635
|
}
|
|
636
636
|
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
headers: { 'Content-Type': 'application/json' },
|
|
640
|
-
body: JSON.stringify({ email }),
|
|
641
|
-
})
|
|
642
|
-
if (!response.ok) {
|
|
643
|
-
throw new Error(`Failed to add team member: ${await response.text()}`)
|
|
644
|
-
}
|
|
645
|
-
const data = await parseJsonResponse<{ member: TeamMember }>(response, 'Team member add')
|
|
637
|
+
const { addTeamMemberWithDeadline } = await import('./team-member-add-workflow.js')
|
|
638
|
+
const member = await addTeamMemberWithDeadline(teamSlug, email, authedFetch)
|
|
646
639
|
if (hasJsonFlag()) {
|
|
647
|
-
printJson({ member
|
|
640
|
+
printJson({ member })
|
|
648
641
|
return
|
|
649
642
|
}
|
|
650
|
-
printLine(`Added team member ${sanitizeTerminalText(
|
|
643
|
+
printLine(`Added team member ${sanitizeTerminalText(member.email)} (${sanitizeTerminalText(member.id)})`)
|
|
651
644
|
}
|
|
652
645
|
|
|
653
646
|
async function removeTeamMember() {
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Cause, Effect, Exit, Result, Schema } from 'effect'
|
|
2
|
+
|
|
3
|
+
import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js'
|
|
4
|
+
import { extractErrorMessage } from './error-response.js'
|
|
5
|
+
import { sanitizeTerminalText } from './json-response.js'
|
|
6
|
+
import { appErrorOf, unreachedCode } from './request-outcome.js'
|
|
7
|
+
|
|
8
|
+
// Loaded only through `await import(...)`, like the dataset workflows.
|
|
9
|
+
|
|
10
|
+
const FAILED = 'Failed to add team member'
|
|
11
|
+
// What the route says when it cannot tell whether the invite was saved (ORI-2383 slice A).
|
|
12
|
+
const SERVER_MAY_BE_SAVED = 'Invite may already be saved. Refresh the member list before inviting again.'
|
|
13
|
+
|
|
14
|
+
// The route saves the address trimmed and lowercased, so the check names that address.
|
|
15
|
+
const mayBeInvited = (teamSlug: string, email: string) => {
|
|
16
|
+
const saved = sanitizeTerminalText(email.trim().toLowerCase())
|
|
17
|
+
return `${saved} may already be invited: run \`orizu teams members list --team ${teamSlug}\` and look for ${saved}. If they are listed, they were invited; don't invite them again.`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const AddReply = Schema.Struct({
|
|
21
|
+
member: Schema.Struct({
|
|
22
|
+
id: Schema.NonEmptyString,
|
|
23
|
+
email: Schema.String,
|
|
24
|
+
}),
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
/** The member as the server sent it; `--json` prints every field it has. */
|
|
28
|
+
export type AddedMember = typeof AddReply.Type['member'] & Readonly<Record<string, unknown>>
|
|
29
|
+
|
|
30
|
+
/** Sends one request; calls `onRequestStart` just before anything goes out. */
|
|
31
|
+
export type TeamMemberAddRequest = (path: string, init: RequestInit, onRequestStart?: () => void) => Promise<Response>
|
|
32
|
+
|
|
33
|
+
// The invite may be saved when the server says so, when the reply was lost,
|
|
34
|
+
// late or unreadable after the request was sent, when a 5xx did not come from
|
|
35
|
+
// the app (a gateway giving up while the app kept working), or when the reply
|
|
36
|
+
// has no member. Any other error reply is a clear refusal.
|
|
37
|
+
class TeamMemberAddFailure extends Error {
|
|
38
|
+
readonly _tag = 'TeamMemberAddFailure'
|
|
39
|
+
constructor(message: string, readonly mayBeSaved: boolean) { super(message) }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// One request, never retried: a retry after a saved invite is refused as
|
|
43
|
+
// "already a member", which hides that the first one worked.
|
|
44
|
+
function memberAdd(teamSlug: string, email: string, request: TeamMemberAddRequest, timeoutMs: number): Effect.Effect<AddedMember, TeamMemberAddFailure> {
|
|
45
|
+
return Effect.suspend(() => {
|
|
46
|
+
let sent = false
|
|
47
|
+
let timedOut = false
|
|
48
|
+
return Effect.tryPromise({
|
|
49
|
+
try: async signal => {
|
|
50
|
+
const controller = new AbortController()
|
|
51
|
+
const abort = () => controller.abort()
|
|
52
|
+
const timer = setTimeout(() => { timedOut = true; controller.abort() }, timeoutMs)
|
|
53
|
+
if (signal.aborted) abort()
|
|
54
|
+
else signal.addEventListener('abort', abort, { once: true })
|
|
55
|
+
try {
|
|
56
|
+
const response = await request(`/api/cli/teams/${encodeURIComponent(teamSlug)}/members`, {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: { 'Content-Type': 'application/json' },
|
|
59
|
+
body: JSON.stringify({ email }),
|
|
60
|
+
signal: controller.signal,
|
|
61
|
+
}, () => { sent = true })
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const appError = await appErrorOf(response.clone())
|
|
64
|
+
const detail = await extractErrorMessage(response)
|
|
65
|
+
if (appError === SERVER_MAY_BE_SAVED) throw new TeamMemberAddFailure(appError, true)
|
|
66
|
+
if (response.status < 400 || (response.status >= 500 && appError === null)) {
|
|
67
|
+
throw new TeamMemberAddFailure(`Member add got no clear answer (status ${response.status}).`, true)
|
|
68
|
+
}
|
|
69
|
+
// The route's own failures already say what failed.
|
|
70
|
+
throw new TeamMemberAddFailure(detail.startsWith('Failed to ') ? detail : `${FAILED}: ${detail}`, false)
|
|
71
|
+
}
|
|
72
|
+
// Reading the body can still fail or stall; that stays under the deadline.
|
|
73
|
+
const body = await response.text()
|
|
74
|
+
let data: unknown
|
|
75
|
+
try {
|
|
76
|
+
data = JSON.parse(body)
|
|
77
|
+
} catch {
|
|
78
|
+
data = undefined
|
|
79
|
+
}
|
|
80
|
+
if (Result.isFailure(Schema.decodeUnknownResult(AddReply)(data))) {
|
|
81
|
+
throw new TeamMemberAddFailure(`Member add returned an unexpected response (status ${response.status}).`, true)
|
|
82
|
+
}
|
|
83
|
+
// Decoding keeps only the named fields; the reply's member is returned whole.
|
|
84
|
+
return (data as { member: AddedMember }).member
|
|
85
|
+
} finally {
|
|
86
|
+
clearTimeout(timer)
|
|
87
|
+
signal.removeEventListener('abort', abort)
|
|
88
|
+
controller.abort()
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
catch: cause => {
|
|
92
|
+
if (cause instanceof TeamMemberAddFailure) return cause
|
|
93
|
+
if (timedOut) return new TeamMemberAddFailure('Member add exceeded its deadline.', sent)
|
|
94
|
+
const unreached = unreachedCode(cause)
|
|
95
|
+
if (unreached) return new TeamMemberAddFailure(`${FAILED}: could not reach the server (${unreached}).`, false)
|
|
96
|
+
const message = cause instanceof Error ? cause.message : String(cause)
|
|
97
|
+
if (!sent || message.startsWith('ORIZU_AUTH_CONTEXT_CHANGED:')) return new TeamMemberAddFailure(message, false)
|
|
98
|
+
// Also reached when a sign-in refresh fails after a 401: still told to
|
|
99
|
+
// check, so say only that the invite did not finish.
|
|
100
|
+
return new TeamMemberAddFailure(`Member add did not finish: ${message}`, true)
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** `orizu teams members add`: the invited member, or an error saying whether the invite may be saved. */
|
|
107
|
+
export async function addTeamMemberWithDeadline(teamSlug: string, email: string, request: TeamMemberAddRequest): Promise<AddedMember> {
|
|
108
|
+
const exit = await Effect.runPromiseExit(memberAdd(teamSlug, email, request, datasetTransferTimeoutMs()).pipe(
|
|
109
|
+
Effect.mapError(failure => new Error(failure.mayBeSaved ? `${failure.message}\n${mayBeInvited(teamSlug, email)}` : failure.message)),
|
|
110
|
+
))
|
|
111
|
+
if (Exit.isSuccess(exit)) return exit.value
|
|
112
|
+
throw Cause.squash(exit.cause)
|
|
113
|
+
}
|