@ttctl/core 0.0.0 → 0.1.0-rc.1
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 +49 -9
- package/dist/__generated__/gateway.d.ts +4546 -0
- package/dist/__generated__/gateway.d.ts.map +1 -0
- package/dist/__generated__/gateway.js +9 -0
- package/dist/__generated__/gateway.js.map +1 -0
- package/dist/__generated__/talent-profile-zod-schemas.d.ts +1187 -0
- package/dist/__generated__/talent-profile-zod-schemas.d.ts.map +1 -0
- package/dist/__generated__/talent-profile-zod-schemas.js +1136 -0
- package/dist/__generated__/talent-profile-zod-schemas.js.map +1 -0
- package/dist/__generated__/talent-profile.d.ts +1397 -0
- package/dist/__generated__/talent-profile.d.ts.map +1 -0
- package/dist/__generated__/talent-profile.js +9 -0
- package/dist/__generated__/talent-profile.js.map +1 -0
- package/dist/__generated__/zod-schemas.d.ts +2895 -0
- package/dist/__generated__/zod-schemas.d.ts.map +1 -0
- package/dist/__generated__/zod-schemas.js +3121 -0
- package/dist/__generated__/zod-schemas.js.map +1 -0
- package/dist/__tests__/fixtures/profile/builders.d.ts +74 -0
- package/dist/__tests__/fixtures/profile/builders.d.ts.map +1 -0
- package/dist/__tests__/fixtures/profile/builders.js +196 -0
- package/dist/__tests__/fixtures/profile/builders.js.map +1 -0
- package/dist/__tests__/fixtures/profile/data.d.ts +39 -0
- package/dist/__tests__/fixtures/profile/data.d.ts.map +1 -0
- package/dist/__tests__/fixtures/profile/data.js +230 -0
- package/dist/__tests__/fixtures/profile/data.js.map +1 -0
- package/dist/__tests__/fixtures/profile/index.d.ts +9 -0
- package/dist/__tests__/fixtures/profile/index.d.ts.map +1 -0
- package/dist/__tests__/fixtures/profile/index.js +10 -0
- package/dist/__tests__/fixtures/profile/index.js.map +1 -0
- package/dist/__tests__/fixtures/profile/types.d.ts +53 -0
- package/dist/__tests__/fixtures/profile/types.d.ts.map +1 -0
- package/dist/__tests__/fixtures/profile/types.js +4 -0
- package/dist/__tests__/fixtures/profile/types.js.map +1 -0
- package/dist/auth/errors.d.ts +82 -0
- package/dist/auth/errors.d.ts.map +1 -0
- package/dist/auth/errors.js +68 -0
- package/dist/auth/errors.js.map +1 -0
- package/dist/auth.d.ts +192 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +294 -0
- package/dist/auth.js.map +1 -0
- package/dist/config.d.ts +212 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +349 -0
- package/dist/config.js.map +1 -0
- package/dist/configLock.d.ts +50 -0
- package/dist/configLock.d.ts.map +1 -0
- package/dist/configLock.js +88 -0
- package/dist/configLock.js.map +1 -0
- package/dist/configWriter.d.ts +97 -0
- package/dist/configWriter.d.ts.map +1 -0
- package/dist/configWriter.js +687 -0
- package/dist/configWriter.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/kill-switch.d.ts +161 -0
- package/dist/kill-switch.d.ts.map +1 -0
- package/dist/kill-switch.js +235 -0
- package/dist/kill-switch.js.map +1 -0
- package/dist/lib/date.d.ts +58 -0
- package/dist/lib/date.d.ts.map +1 -0
- package/dist/lib/date.js +104 -0
- package/dist/lib/date.js.map +1 -0
- package/dist/lib/diagnostic-log.d.ts +159 -0
- package/dist/lib/diagnostic-log.d.ts.map +1 -0
- package/dist/lib/diagnostic-log.js +186 -0
- package/dist/lib/diagnostic-log.js.map +1 -0
- package/dist/lib/package-version.d.ts +19 -0
- package/dist/lib/package-version.d.ts.map +1 -0
- package/dist/lib/package-version.js +38 -0
- package/dist/lib/package-version.js.map +1 -0
- package/dist/lib/redact.d.ts +153 -0
- package/dist/lib/redact.d.ts.map +1 -0
- package/dist/lib/redact.js +207 -0
- package/dist/lib/redact.js.map +1 -0
- package/dist/lib/text.d.ts +14 -0
- package/dist/lib/text.d.ts.map +1 -0
- package/dist/lib/text.js +21 -0
- package/dist/lib/text.js.map +1 -0
- package/dist/lib/wire-shape.d.ts +131 -0
- package/dist/lib/wire-shape.d.ts.map +1 -0
- package/dist/lib/wire-shape.js +376 -0
- package/dist/lib/wire-shape.js.map +1 -0
- package/dist/onepassword.d.ts +29 -0
- package/dist/onepassword.d.ts.map +1 -0
- package/dist/onepassword.js +112 -0
- package/dist/onepassword.js.map +1 -0
- package/dist/services/_shared/transport.d.ts +148 -0
- package/dist/services/_shared/transport.d.ts.map +1 -0
- package/dist/services/_shared/transport.js +102 -0
- package/dist/services/_shared/transport.js.map +1 -0
- package/dist/services/applications/index.d.ts +210 -0
- package/dist/services/applications/index.d.ts.map +1 -0
- package/dist/services/applications/index.js +240 -0
- package/dist/services/applications/index.js.map +1 -0
- package/dist/services/availability/index.d.ts +254 -0
- package/dist/services/availability/index.d.ts.map +1 -0
- package/dist/services/availability/index.js +310 -0
- package/dist/services/availability/index.js.map +1 -0
- package/dist/services/contracts/index.d.ts +132 -0
- package/dist/services/contracts/index.d.ts.map +1 -0
- package/dist/services/contracts/index.js +211 -0
- package/dist/services/contracts/index.js.map +1 -0
- package/dist/services/engagements/index.d.ts +504 -0
- package/dist/services/engagements/index.d.ts.map +1 -0
- package/dist/services/engagements/index.js +613 -0
- package/dist/services/engagements/index.js.map +1 -0
- package/dist/services/jobs/index.d.ts +490 -0
- package/dist/services/jobs/index.d.ts.map +1 -0
- package/dist/services/jobs/index.js +753 -0
- package/dist/services/jobs/index.js.map +1 -0
- package/dist/services/payments/index.d.ts +415 -0
- package/dist/services/payments/index.d.ts.map +1 -0
- package/dist/services/payments/index.js +636 -0
- package/dist/services/payments/index.js.map +1 -0
- package/dist/services/profile/__tests__/fixtures.d.ts +214 -0
- package/dist/services/profile/__tests__/fixtures.d.ts.map +1 -0
- package/dist/services/profile/__tests__/fixtures.js +176 -0
- package/dist/services/profile/__tests__/fixtures.js.map +1 -0
- package/dist/services/profile/basic/index.d.ts +390 -0
- package/dist/services/profile/basic/index.d.ts.map +1 -0
- package/dist/services/profile/basic/index.js +1007 -0
- package/dist/services/profile/basic/index.js.map +1 -0
- package/dist/services/profile/certifications/index.d.ts +74 -0
- package/dist/services/profile/certifications/index.d.ts.map +1 -0
- package/dist/services/profile/certifications/index.js +169 -0
- package/dist/services/profile/certifications/index.js.map +1 -0
- package/dist/services/profile/education/index.d.ts +73 -0
- package/dist/services/profile/education/index.d.ts.map +1 -0
- package/dist/services/profile/education/index.js +168 -0
- package/dist/services/profile/education/index.js.map +1 -0
- package/dist/services/profile/employment/index.d.ts +111 -0
- package/dist/services/profile/employment/index.d.ts.map +1 -0
- package/dist/services/profile/employment/index.js +202 -0
- package/dist/services/profile/employment/index.js.map +1 -0
- package/dist/services/profile/external/index.d.ts +219 -0
- package/dist/services/profile/external/index.d.ts.map +1 -0
- package/dist/services/profile/external/index.js +560 -0
- package/dist/services/profile/external/index.js.map +1 -0
- package/dist/services/profile/index.d.ts +24 -0
- package/dist/services/profile/index.d.ts.map +1 -0
- package/dist/services/profile/index.js +26 -0
- package/dist/services/profile/index.js.map +1 -0
- package/dist/services/profile/industries/index.d.ts +130 -0
- package/dist/services/profile/industries/index.d.ts.map +1 -0
- package/dist/services/profile/industries/index.js +292 -0
- package/dist/services/profile/industries/index.js.map +1 -0
- package/dist/services/profile/portfolio/index.d.ts +352 -0
- package/dist/services/profile/portfolio/index.d.ts.map +1 -0
- package/dist/services/profile/portfolio/index.js +833 -0
- package/dist/services/profile/portfolio/index.js.map +1 -0
- package/dist/services/profile/resume/index.d.ts +60 -0
- package/dist/services/profile/resume/index.d.ts.map +1 -0
- package/dist/services/profile/resume/index.js +212 -0
- package/dist/services/profile/resume/index.js.map +1 -0
- package/dist/services/profile/reviews/index.d.ts +137 -0
- package/dist/services/profile/reviews/index.d.ts.map +1 -0
- package/dist/services/profile/reviews/index.js +431 -0
- package/dist/services/profile/reviews/index.js.map +1 -0
- package/dist/services/profile/shared.d.ts +127 -0
- package/dist/services/profile/shared.d.ts.map +1 -0
- package/dist/services/profile/shared.js +155 -0
- package/dist/services/profile/shared.js.map +1 -0
- package/dist/services/profile/skills/index.d.ts +212 -0
- package/dist/services/profile/skills/index.d.ts.map +1 -0
- package/dist/services/profile/skills/index.js +461 -0
- package/dist/services/profile/skills/index.js.map +1 -0
- package/dist/services/profile/visas/index.d.ts +74 -0
- package/dist/services/profile/visas/index.d.ts.map +1 -0
- package/dist/services/profile/visas/index.js +306 -0
- package/dist/services/profile/visas/index.js.map +1 -0
- package/dist/services/timesheet/index.d.ts +326 -0
- package/dist/services/timesheet/index.d.ts.map +1 -0
- package/dist/services/timesheet/index.js +324 -0
- package/dist/services/timesheet/index.js.map +1 -0
- package/dist/services/translations.d.ts +79 -0
- package/dist/services/translations.d.ts.map +1 -0
- package/dist/services/translations.js +136 -0
- package/dist/services/translations.js.map +1 -0
- package/dist/transport-resilience.d.ts +136 -0
- package/dist/transport-resilience.d.ts.map +1 -0
- package/dist/transport-resilience.js +247 -0
- package/dist/transport-resilience.js.map +1 -0
- package/dist/transport.d.ts +408 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +691 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +41 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/package.json +40 -12
- package/index.js +0 -7
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (C) 2026 Oleksii PELYKH
|
|
3
|
+
import { AuthRevokedError, TtctlError } from "../../../auth/errors.js";
|
|
4
|
+
import { impersonatedTransport } from "../../../transport.js";
|
|
5
|
+
import { extractProfileId, isAuthRevokedExtensionCode } from "../shared.js";
|
|
6
|
+
export class VisasError extends Error {
|
|
7
|
+
code;
|
|
8
|
+
name = "VisasError";
|
|
9
|
+
constructor(code, message, options) {
|
|
10
|
+
super(message, options);
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Map a server `TravelVisa` node to the typed {@link TravelVisa}. */
|
|
15
|
+
function mapTravelVisaNode(node) {
|
|
16
|
+
const country = (node["country"] ?? {});
|
|
17
|
+
const id = node["id"];
|
|
18
|
+
return {
|
|
19
|
+
id: typeof id === "string" ? id : "",
|
|
20
|
+
countryId: country.id ?? "",
|
|
21
|
+
countryName: country.name ?? "",
|
|
22
|
+
visaType: node["visaType"] ?? "",
|
|
23
|
+
expiryDate: node["expiryDate"] ?? null,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Common "200 with errors" shape handler. Returns the unwrapped payload
|
|
28
|
+
* (the value of the single root data field) as `unknown`; callers
|
|
29
|
+
* narrow to their per-operation payload shape at the call site.
|
|
30
|
+
*/
|
|
31
|
+
function unwrapResponse(res, operationName) {
|
|
32
|
+
if (res.status === 401) {
|
|
33
|
+
throw new AuthRevokedError("Session is invalid or expired.");
|
|
34
|
+
}
|
|
35
|
+
if (res.status < 200 || res.status >= 300) {
|
|
36
|
+
throw new VisasError("UNKNOWN", `${operationName} returned HTTP ${res.status.toString()}`);
|
|
37
|
+
}
|
|
38
|
+
const body = res.body;
|
|
39
|
+
if (body && Array.isArray(body.errors) && body.errors.length > 0) {
|
|
40
|
+
const first = body.errors[0];
|
|
41
|
+
if (isAuthRevokedExtensionCode(first?.extensions?.code)) {
|
|
42
|
+
throw new AuthRevokedError("Session is invalid or expired.");
|
|
43
|
+
}
|
|
44
|
+
throw new VisasError("GRAPHQL_ERROR", `${operationName} failed: ${first?.message ?? "GraphQL error"}`);
|
|
45
|
+
}
|
|
46
|
+
if (!body?.data) {
|
|
47
|
+
throw new VisasError("UNKNOWN", `${operationName} response had no \`data\` field`);
|
|
48
|
+
}
|
|
49
|
+
const keys = Object.keys(body.data);
|
|
50
|
+
if (keys.length === 0) {
|
|
51
|
+
throw new VisasError("UNKNOWN", `${operationName} response had empty \`data\``);
|
|
52
|
+
}
|
|
53
|
+
const firstKey = keys[0];
|
|
54
|
+
const payload = body.data[firstKey];
|
|
55
|
+
if (payload === null || payload === undefined) {
|
|
56
|
+
throw new VisasError("UNKNOWN", `${operationName} response had \`null\` payload`);
|
|
57
|
+
}
|
|
58
|
+
return payload;
|
|
59
|
+
}
|
|
60
|
+
function rejectIfUserErrors(errors, operationName) {
|
|
61
|
+
if (Array.isArray(errors) && errors.length > 0) {
|
|
62
|
+
const first = errors[0];
|
|
63
|
+
const fieldHint = first?.key ? ` (${first.key})` : "";
|
|
64
|
+
throw new VisasError("USER_ERROR", `${operationName} rejected${fieldHint}: ${first?.message ?? "unknown error"}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function withTransportErrors(operationName, fn) {
|
|
68
|
+
try {
|
|
69
|
+
return await fn();
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
if (err instanceof TtctlError)
|
|
73
|
+
throw err;
|
|
74
|
+
if (err instanceof VisasError)
|
|
75
|
+
throw err;
|
|
76
|
+
throw new VisasError("NETWORK_ERROR", `${operationName} request failed: ${err.message}`, {
|
|
77
|
+
cause: err,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/* -------------------------------------------------------------------------- */
|
|
82
|
+
/* Operations */
|
|
83
|
+
/* -------------------------------------------------------------------------- */
|
|
84
|
+
/**
|
|
85
|
+
* Full-document `getTravelVisas` query (talent-profile).
|
|
86
|
+
*
|
|
87
|
+
* The `Profile.travelVisas` field is only defined on the `talent-profile`
|
|
88
|
+
* schema — `mobile-gateway` does not expose it (verified against
|
|
89
|
+
* `research/graphql/talent_profile/operations/getTravelVisas.graphql` and
|
|
90
|
+
* the synthesized SDLs under `research/graphql/`). Sibling mutations
|
|
91
|
+
* (`createTravelVisa`, `updateTravelVisa`, `removeTravelVisa`) already
|
|
92
|
+
* route through `talent-profile` for the same reason; this read aligns
|
|
93
|
+
* with that surface choice.
|
|
94
|
+
*/
|
|
95
|
+
const GET_TRAVEL_VISAS_QUERY = `query getTravelVisas($profileId: ID!) {
|
|
96
|
+
profile(id: $profileId) {
|
|
97
|
+
id
|
|
98
|
+
travelVisas {
|
|
99
|
+
nodes {
|
|
100
|
+
id
|
|
101
|
+
country {
|
|
102
|
+
id
|
|
103
|
+
name
|
|
104
|
+
}
|
|
105
|
+
visaType
|
|
106
|
+
expiryDate
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}`;
|
|
111
|
+
/**
|
|
112
|
+
* Fetch the signed-in user's travel-visa records. Empty list returns `[]`,
|
|
113
|
+
* never `null`.
|
|
114
|
+
*/
|
|
115
|
+
export async function list(token) {
|
|
116
|
+
const profileId = await extractProfileId(token);
|
|
117
|
+
const res = await withTransportErrors("getTravelVisas", async () => impersonatedTransport({
|
|
118
|
+
surface: "talent-profile",
|
|
119
|
+
authToken: token,
|
|
120
|
+
body: {
|
|
121
|
+
operationName: "getTravelVisas",
|
|
122
|
+
query: GET_TRAVEL_VISAS_QUERY,
|
|
123
|
+
variables: { profileId },
|
|
124
|
+
},
|
|
125
|
+
}));
|
|
126
|
+
const profile = unwrapResponse(res, "getTravelVisas");
|
|
127
|
+
return profile.travelVisas.nodes.map(mapTravelVisaNode);
|
|
128
|
+
}
|
|
129
|
+
const CREATE_TRAVEL_VISA_MUTATION = `mutation createTravelVisa($input: CreateTravelVisaInput!) {
|
|
130
|
+
createTravelVisa(input: $input) {
|
|
131
|
+
profile {
|
|
132
|
+
id
|
|
133
|
+
travelVisas {
|
|
134
|
+
nodes {
|
|
135
|
+
id
|
|
136
|
+
country {
|
|
137
|
+
id
|
|
138
|
+
name
|
|
139
|
+
}
|
|
140
|
+
visaType
|
|
141
|
+
expiryDate
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
errors {
|
|
146
|
+
code
|
|
147
|
+
key
|
|
148
|
+
message
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}`;
|
|
152
|
+
/**
|
|
153
|
+
* Create a new travel-visa record.
|
|
154
|
+
*
|
|
155
|
+
* Both `countryId` and `visaType` are required by the AC; `expiryDate`
|
|
156
|
+
* is optional. The wire-side error path returns `errors[]` on the
|
|
157
|
+
* mutation payload when the server rejects (e.g. unknown country,
|
|
158
|
+
* malformed date) — those collapse into `USER_ERROR`.
|
|
159
|
+
*/
|
|
160
|
+
export async function add(token, input) {
|
|
161
|
+
if (!input.countryId || input.countryId.trim() === "") {
|
|
162
|
+
throw new VisasError("VALIDATION_ERROR", "Travel visa requires a non-empty `countryId`.");
|
|
163
|
+
}
|
|
164
|
+
if (!input.visaType || input.visaType.trim() === "") {
|
|
165
|
+
throw new VisasError("VALIDATION_ERROR", "Travel visa requires a non-empty `visaType`.");
|
|
166
|
+
}
|
|
167
|
+
const profileId = await extractProfileId(token);
|
|
168
|
+
const variables = {
|
|
169
|
+
input: { profileId, travelVisa: input },
|
|
170
|
+
};
|
|
171
|
+
const res = await withTransportErrors("createTravelVisa", async () => impersonatedTransport({
|
|
172
|
+
surface: "talent-profile",
|
|
173
|
+
authToken: token,
|
|
174
|
+
body: {
|
|
175
|
+
operationName: "createTravelVisa",
|
|
176
|
+
query: CREATE_TRAVEL_VISA_MUTATION,
|
|
177
|
+
variables,
|
|
178
|
+
},
|
|
179
|
+
}));
|
|
180
|
+
const payload = unwrapResponse(res, "createTravelVisa");
|
|
181
|
+
rejectIfUserErrors(payload.errors, "createTravelVisa");
|
|
182
|
+
if (!payload.profile) {
|
|
183
|
+
throw new VisasError("UNKNOWN", "createTravelVisa succeeded but response had no profile payload.");
|
|
184
|
+
}
|
|
185
|
+
return payload.profile.travelVisas.nodes.map(mapTravelVisaNode);
|
|
186
|
+
}
|
|
187
|
+
const UPDATE_TRAVEL_VISA_MUTATION = `mutation updateTravelVisa($input: UpdateTravelVisaInput!) {
|
|
188
|
+
updateTravelVisa(input: $input) {
|
|
189
|
+
profile {
|
|
190
|
+
id
|
|
191
|
+
travelVisas {
|
|
192
|
+
nodes {
|
|
193
|
+
id
|
|
194
|
+
country {
|
|
195
|
+
id
|
|
196
|
+
name
|
|
197
|
+
}
|
|
198
|
+
visaType
|
|
199
|
+
expiryDate
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
errors {
|
|
204
|
+
code
|
|
205
|
+
key
|
|
206
|
+
message
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}`;
|
|
210
|
+
/**
|
|
211
|
+
* Update a travel-visa record by id.
|
|
212
|
+
*
|
|
213
|
+
* `UpdateTravelVisaInput.travelVisa` is treated as a **full-document
|
|
214
|
+
* replacement** by the server — `countryId` and `visaType` are
|
|
215
|
+
* non-nullable on the input even when the caller doesn't want to change
|
|
216
|
+
* them. Sending only the patched field returns
|
|
217
|
+
* `Variable $input of type UpdateTravelVisaInput! was provided invalid
|
|
218
|
+
* value for travelVisa.countryId/visaType (Expected value to not be
|
|
219
|
+
* null)` (verified live 2026-05-15, see #317).
|
|
220
|
+
*
|
|
221
|
+
* To preserve the partial-update UX over the full-replace wire shape,
|
|
222
|
+
* this function does read-modify-write: fetch the current record via
|
|
223
|
+
* {@link list}, merge caller's `changes` on top of the current state,
|
|
224
|
+
* then send the merged input. Same pattern as
|
|
225
|
+
* `services/profile/portfolio/index.ts` `update` (PR #323 / #314).
|
|
226
|
+
*/
|
|
227
|
+
export async function update(token, id, changes) {
|
|
228
|
+
if (Object.keys(changes).length === 0) {
|
|
229
|
+
throw new VisasError("VALIDATION_ERROR", "Travel visa update requires at least one field.");
|
|
230
|
+
}
|
|
231
|
+
// Read-modify-write: see function doc-comment.
|
|
232
|
+
const current = (await list(token)).find((v) => v.id === id);
|
|
233
|
+
if (!current) {
|
|
234
|
+
throw new VisasError("VALIDATION_ERROR", `Travel visa ${id} not found.`);
|
|
235
|
+
}
|
|
236
|
+
// `expiryDate` is `string | null` on the read side but `string | undefined`
|
|
237
|
+
// on the write side; the conditional spread satisfies
|
|
238
|
+
// `exactOptionalPropertyTypes: true` (omit when null, value when string).
|
|
239
|
+
const merged = {
|
|
240
|
+
countryId: current.countryId,
|
|
241
|
+
visaType: current.visaType,
|
|
242
|
+
...(current.expiryDate !== null && { expiryDate: current.expiryDate }),
|
|
243
|
+
...changes,
|
|
244
|
+
};
|
|
245
|
+
const variables = {
|
|
246
|
+
input: { travelVisaId: id, travelVisa: merged },
|
|
247
|
+
};
|
|
248
|
+
const res = await withTransportErrors("updateTravelVisa", async () => impersonatedTransport({
|
|
249
|
+
surface: "talent-profile",
|
|
250
|
+
authToken: token,
|
|
251
|
+
body: {
|
|
252
|
+
operationName: "updateTravelVisa",
|
|
253
|
+
query: UPDATE_TRAVEL_VISA_MUTATION,
|
|
254
|
+
variables,
|
|
255
|
+
},
|
|
256
|
+
}));
|
|
257
|
+
const payload = unwrapResponse(res, "updateTravelVisa");
|
|
258
|
+
rejectIfUserErrors(payload.errors, "updateTravelVisa");
|
|
259
|
+
if (!payload.profile) {
|
|
260
|
+
throw new VisasError("UNKNOWN", "updateTravelVisa succeeded but response had no profile payload.");
|
|
261
|
+
}
|
|
262
|
+
return payload.profile.travelVisas.nodes.map(mapTravelVisaNode);
|
|
263
|
+
}
|
|
264
|
+
const REMOVE_TRAVEL_VISA_MUTATION = `mutation removeTravelVisa($input: RemoveTravelVisaInput!) {
|
|
265
|
+
removeTravelVisa(input: $input) {
|
|
266
|
+
profile {
|
|
267
|
+
id
|
|
268
|
+
travelVisas {
|
|
269
|
+
nodes {
|
|
270
|
+
id
|
|
271
|
+
country {
|
|
272
|
+
id
|
|
273
|
+
name
|
|
274
|
+
}
|
|
275
|
+
visaType
|
|
276
|
+
expiryDate
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
errors {
|
|
281
|
+
code
|
|
282
|
+
key
|
|
283
|
+
message
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}`;
|
|
287
|
+
/** Remove a travel-visa record by id. */
|
|
288
|
+
export async function remove(token, id) {
|
|
289
|
+
const variables = { input: { travelVisaId: id } };
|
|
290
|
+
const res = await withTransportErrors("removeTravelVisa", async () => impersonatedTransport({
|
|
291
|
+
surface: "talent-profile",
|
|
292
|
+
authToken: token,
|
|
293
|
+
body: {
|
|
294
|
+
operationName: "removeTravelVisa",
|
|
295
|
+
query: REMOVE_TRAVEL_VISA_MUTATION,
|
|
296
|
+
variables,
|
|
297
|
+
},
|
|
298
|
+
}));
|
|
299
|
+
const payload = unwrapResponse(res, "removeTravelVisa");
|
|
300
|
+
rejectIfUserErrors(payload.errors, "removeTravelVisa");
|
|
301
|
+
if (!payload.profile) {
|
|
302
|
+
throw new VisasError("UNKNOWN", "removeTravelVisa succeeded but response had no profile payload.");
|
|
303
|
+
}
|
|
304
|
+
return payload.profile.travelVisas.nodes.map(mapTravelVisaNode);
|
|
305
|
+
}
|
|
306
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/services/profile/visas/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import type { DryRunPreview } from "../../transport.js";
|
|
2
|
+
/**
|
|
3
|
+
* Timesheet-domain error codes. Mirrors the `EngagementsError` /
|
|
4
|
+
* `ApplicationsError` shape per project convention.
|
|
5
|
+
*
|
|
6
|
+
* - `NO_VIEWER`: HTTP 200 + `data.viewer === null` (defensive — should
|
|
7
|
+
* never happen with a valid token).
|
|
8
|
+
* - `NOT_FOUND`: the supplied id (billing cycle or engagement) does
|
|
9
|
+
* not resolve. The wire shape can surface this either as a top-level
|
|
10
|
+
* `Record not found` GraphQL error OR as `data.node === null` /
|
|
11
|
+
* `data.viewer.jobActivityItem === null`.
|
|
12
|
+
* - `NO_ENGAGEMENT`: the activity row exists but has no engagement
|
|
13
|
+
* (e.g., an interview-only row that never became an engagement). The
|
|
14
|
+
* timesheet domain can't operate on such rows.
|
|
15
|
+
* - `NO_CURRENT_CYCLE`: {@link resolveCurrentCycle} returned zero
|
|
16
|
+
* matches — used by the CLI's submit auto-resolve path so callers
|
|
17
|
+
* can distinguish "nothing to submit" from "id was invalid".
|
|
18
|
+
* - `MULTIPLE_CURRENT_CYCLES`: {@link resolveCurrentCycle} returned
|
|
19
|
+
* more than one match — used by the CLI's submit auto-resolve path
|
|
20
|
+
* so callers can prompt the user to disambiguate. The error carries
|
|
21
|
+
* the candidate list on `EngagementsError.cause` (not exposed via
|
|
22
|
+
* typing; callers use `resolveCurrentCycle` directly when they need
|
|
23
|
+
* structured access).
|
|
24
|
+
* - `GRAPHQL_ERROR`: top-level `errors[]` from the gateway, not an
|
|
25
|
+
* auth-revoked extension and not a `Record not found`.
|
|
26
|
+
* - `MUTATION_ERROR`: the `MutationResult.errors[]` payload from
|
|
27
|
+
* `SubmitTimesheet` (the wire operation succeeded at GraphQL level,
|
|
28
|
+
* but the submission itself reports per-field errors — overdue,
|
|
29
|
+
* missing required hours, etc.).
|
|
30
|
+
* - `NETWORK_ERROR`, `UNKNOWN`: standard transport failure modes.
|
|
31
|
+
*
|
|
32
|
+
* Auth-revoked failures throw `AuthRevokedError` (cross-cutting
|
|
33
|
+
* `TtctlError` subclass per #77), not a code on this enum.
|
|
34
|
+
*/
|
|
35
|
+
export type TimesheetErrorCode = "NO_VIEWER" | "NOT_FOUND" | "NO_ENGAGEMENT" | "NO_CURRENT_CYCLE" | "MULTIPLE_CURRENT_CYCLES" | "GRAPHQL_ERROR" | "MUTATION_ERROR" | "NETWORK_ERROR" | "WIRE_SHAPE_ERROR" | "UNKNOWN";
|
|
36
|
+
export declare class TimesheetError extends Error {
|
|
37
|
+
readonly code: TimesheetErrorCode;
|
|
38
|
+
readonly name = "TimesheetError";
|
|
39
|
+
constructor(code: TimesheetErrorCode, message: string, options?: {
|
|
40
|
+
cause?: unknown;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Minimum-commitment payload mirrored from the captured
|
|
45
|
+
* `minimumCommitmentData` fragment. When `applicable === false`, the
|
|
46
|
+
* server explains via `reasonNotApplicable`.
|
|
47
|
+
*/
|
|
48
|
+
export interface TimesheetMinimumCommitment {
|
|
49
|
+
applicable: boolean;
|
|
50
|
+
minimumHours: number | null;
|
|
51
|
+
reasonNotApplicable: string | null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Reference to the engagement+job a billing cycle belongs to. The id
|
|
55
|
+
* carried here is the underlying `TalentEngagement.id`, NOT the
|
|
56
|
+
* `JobActivityItem.id` exposed by `engagements list`. Callers that
|
|
57
|
+
* need to cross-reference back to the engagements row should use
|
|
58
|
+
* `engagements list` and match on `engagementId`.
|
|
59
|
+
*/
|
|
60
|
+
export interface TimesheetEngagementRef {
|
|
61
|
+
id: string;
|
|
62
|
+
job: {
|
|
63
|
+
id: string;
|
|
64
|
+
title: string | null;
|
|
65
|
+
client: {
|
|
66
|
+
id: string;
|
|
67
|
+
fullName: string | null;
|
|
68
|
+
} | null;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* One row in the timesheet list — surfaced by `timesheet list`. Wire
|
|
73
|
+
* type for `hours` is a string (e.g., `"8.0"`) — preserved as-is so
|
|
74
|
+
* the CLI can render it verbatim without decimal-precision surprises.
|
|
75
|
+
*
|
|
76
|
+
* `timesheetSubmissionOpenDatetime` / `timesheetSubmissionDeadlineDatetime`
|
|
77
|
+
* carry the wire ISO-8601 datetime strings. The CLI may render only
|
|
78
|
+
* the date portion in pretty output.
|
|
79
|
+
*/
|
|
80
|
+
export interface TimesheetListItem {
|
|
81
|
+
/** BillingCycle.id — the public timesheet id. */
|
|
82
|
+
id: string;
|
|
83
|
+
startDate: string;
|
|
84
|
+
endDate: string;
|
|
85
|
+
hours: string;
|
|
86
|
+
minimumCommitment: TimesheetMinimumCommitment | null;
|
|
87
|
+
timesheetOverdue: boolean;
|
|
88
|
+
timesheetSubmissionOpenDatetime: string | null;
|
|
89
|
+
timesheetSubmissionDeadlineDatetime: string | null;
|
|
90
|
+
timesheetSubmitted: boolean;
|
|
91
|
+
engagement: TimesheetEngagementRef;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* One time-entry within a timesheet (per-day duration). `duration` is
|
|
95
|
+
* the canonical wire field — a **string-encoded decimal** in **minutes**
|
|
96
|
+
* (e.g., `"480.0"` for an 8-hour day, `"0.0"` for a zero-hour day).
|
|
97
|
+
* `note` may be empty/null; `isDayOff === true` rows represent a marked
|
|
98
|
+
* day off and typically carry `duration === "0.0"`.
|
|
99
|
+
*
|
|
100
|
+
* **Wire-shape history**: this field was declared as `duration: number`
|
|
101
|
+
* (presumed seconds) until 2026-05-14. The live mobile-gateway returns
|
|
102
|
+
* a string-encoded minutes value, empirically captured during the first
|
|
103
|
+
* end-to-end `SubmitTimesheet` mutation run. The previous declaration
|
|
104
|
+
* caused `ttctl timesheet show` to render an 8-hour day as `0.13h`
|
|
105
|
+
* (because `"480.0" / 3600 ≈ 0.133`). See
|
|
106
|
+
* `.tmp/timesheet-submit-e2e-20260514/{01-before-show,03-submit}.json`
|
|
107
|
+
* for the originating capture.
|
|
108
|
+
*/
|
|
109
|
+
export interface TimesheetRecord {
|
|
110
|
+
date: string;
|
|
111
|
+
duration: string;
|
|
112
|
+
note: string | null;
|
|
113
|
+
isDayOff: boolean;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Detail-view shape for `timesheet show <id>`. Extends
|
|
117
|
+
* {@link TimesheetListItem} with the timesheet records, the comment,
|
|
118
|
+
* the rate-card snapshot, and the points-of-contact projection from
|
|
119
|
+
* the engagement.
|
|
120
|
+
*
|
|
121
|
+
* Field selection mirrors the captured `timesheetDetailsFields`
|
|
122
|
+
* fragment.
|
|
123
|
+
*/
|
|
124
|
+
export interface TimesheetDetail extends TimesheetListItem {
|
|
125
|
+
timesheetUrl: string | null;
|
|
126
|
+
timesheetComment: string | null;
|
|
127
|
+
timesheetRecords: TimesheetRecord[];
|
|
128
|
+
actualAgreement: {
|
|
129
|
+
applicationRate: string | null;
|
|
130
|
+
talentHourlyRate: string | null;
|
|
131
|
+
marketplaceMargin: string | null;
|
|
132
|
+
} | null;
|
|
133
|
+
engagement: TimesheetEngagementRef & {
|
|
134
|
+
expectedHours: number | null;
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Optional list filter.
|
|
139
|
+
*/
|
|
140
|
+
export interface ListOptions {
|
|
141
|
+
/**
|
|
142
|
+
* When set, scope the listing to the given engagement (the
|
|
143
|
+
* `JobActivityItem.id` from `engagements list`, NOT the underlying
|
|
144
|
+
* `TalentEngagement.id`). Switches the wire query from
|
|
145
|
+
* `PendingTimesheets` (viewer-wide pending-only) to
|
|
146
|
+
* `Timesheets(jobActivityItemId)` (all cycles for that engagement,
|
|
147
|
+
* regardless of submission state).
|
|
148
|
+
*/
|
|
149
|
+
engagement?: string;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Result of {@link resolveCurrentCycle}.
|
|
153
|
+
*
|
|
154
|
+
* - `kind: "found"` — exactly one cycle's submission window contains
|
|
155
|
+
* "today" AND it's not yet submitted. Submit by `cycle.id`.
|
|
156
|
+
* - `kind: "none"` — zero cycles match. Either the user is too early
|
|
157
|
+
* (before the next cycle's `submissionOpenDatetime`) or too late
|
|
158
|
+
* (after the deadline of every pending cycle).
|
|
159
|
+
* - `kind: "multiple"` — more than one cycle matches. The CLI prompts
|
|
160
|
+
* the user to disambiguate by explicit id.
|
|
161
|
+
*/
|
|
162
|
+
export type CurrentCycleResolution = {
|
|
163
|
+
kind: "found";
|
|
164
|
+
cycle: TimesheetListItem;
|
|
165
|
+
} | {
|
|
166
|
+
kind: "none";
|
|
167
|
+
} | {
|
|
168
|
+
kind: "multiple";
|
|
169
|
+
candidates: TimesheetListItem[];
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* Per-mutation option object for the dry-run short-circuit. Mirrors
|
|
173
|
+
* `engagements.DryRunOptions` (issue #163) and the CLI-global
|
|
174
|
+
* `--dry-run` flag (#52). When `dryRun === true`, {@link submit}
|
|
175
|
+
* builds a {@link DryRunPreview} and returns `{ kind: "preview",
|
|
176
|
+
* preview }` WITHOUT invoking the gateway transport. Default `false`
|
|
177
|
+
* — the apply path runs and a `{ kind: "applied", result }` outcome
|
|
178
|
+
* is returned.
|
|
179
|
+
*
|
|
180
|
+
* **`submit` specifics**: the apply path takes `id: string`
|
|
181
|
+
* (BillingCycle.id) and submits unconditionally. The dry-run path
|
|
182
|
+
* builds the preview directly off the supplied id. When the CLI
|
|
183
|
+
* runner is in auto-resolve mode (no positional id, dry-run engaged),
|
|
184
|
+
* it stamps a literal placeholder string (e.g.
|
|
185
|
+
* `<auto-resolved-at-apply-time>`) into `id` so the preview's
|
|
186
|
+
* `variables.id` is well-named and clearly NOT a real cycle id; the
|
|
187
|
+
* apply path resolves the real id via
|
|
188
|
+
* {@link resolveCurrentCycle} before submitting.
|
|
189
|
+
*/
|
|
190
|
+
export interface DryRunOptions {
|
|
191
|
+
dryRun?: boolean;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Apply-path outcome for {@link submit}. Wraps the
|
|
195
|
+
* post-submission {@link TimesheetDetail} in a discriminated union so
|
|
196
|
+
* callers can branch deterministically between the apply path
|
|
197
|
+
* (`kind: "applied"`) and the dry-run path
|
|
198
|
+
* (`kind: "preview"`, see {@link TimesheetDryRunPreviewOutcome}).
|
|
199
|
+
*/
|
|
200
|
+
export interface TimesheetSubmitAppliedOutcome {
|
|
201
|
+
kind: "applied";
|
|
202
|
+
result: TimesheetDetail;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Dry-run outcome for {@link submit}. Carries the
|
|
206
|
+
* {@link DryRunPreview} (operation name, surface, transport,
|
|
207
|
+
* endpoint, variables payload, redacted bearer header) — emitted
|
|
208
|
+
* verbatim by the CLI's dry-run envelope (`emitDryRunSuccess`).
|
|
209
|
+
*/
|
|
210
|
+
export interface TimesheetDryRunPreviewOutcome {
|
|
211
|
+
kind: "preview";
|
|
212
|
+
preview: DryRunPreview;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Discriminated-union return type for {@link submit}.
|
|
216
|
+
*/
|
|
217
|
+
export type SubmitOutcome = TimesheetSubmitAppliedOutcome | TimesheetDryRunPreviewOutcome;
|
|
218
|
+
/**
|
|
219
|
+
* Optional inputs for {@link resolveCurrentCycle}.
|
|
220
|
+
*/
|
|
221
|
+
export interface ResolveCurrentCycleOptions {
|
|
222
|
+
/**
|
|
223
|
+
* Same semantic as {@link ListOptions.engagement}: scope the
|
|
224
|
+
* resolve to one engagement. When set, the helper switches its
|
|
225
|
+
* underlying query from `PendingTimesheets` to
|
|
226
|
+
* `Timesheets(jobActivityItemId)` and filters to
|
|
227
|
+
* `timesheetSubmitted === false` client-side.
|
|
228
|
+
*/
|
|
229
|
+
engagement?: string;
|
|
230
|
+
/**
|
|
231
|
+
* Override "now" for deterministic tests. Defaults to
|
|
232
|
+
* `new Date()` at call time.
|
|
233
|
+
*/
|
|
234
|
+
now?: Date;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* List the signed-in user's timesheet billing cycles.
|
|
238
|
+
*
|
|
239
|
+
* Default (no `engagement` option): uses `PendingTimesheets` to fetch
|
|
240
|
+
* viewer-wide cycles that still need submission. This is the
|
|
241
|
+
* "what needs my attention" view.
|
|
242
|
+
*
|
|
243
|
+
* With `engagement` option: uses `Timesheets($jobActivityItemId)` to
|
|
244
|
+
* fetch ALL cycles for that engagement (regardless of submission
|
|
245
|
+
* state). The argument is the public `JobActivityItem.id` exposed by
|
|
246
|
+
* `engagements list`.
|
|
247
|
+
*
|
|
248
|
+
* The returned array preserves server order; the CLI / MCP do not
|
|
249
|
+
* re-sort.
|
|
250
|
+
*/
|
|
251
|
+
export declare function list(token: string, opts?: ListOptions): Promise<TimesheetListItem[]>;
|
|
252
|
+
/**
|
|
253
|
+
* Fetch a single timesheet's detail by `BillingCycle.id`.
|
|
254
|
+
*
|
|
255
|
+
* Uses the gateway's `node(id)` polymorphic root with the captured
|
|
256
|
+
* `timesheetDetailsFields` fragment. Throws
|
|
257
|
+
* `TimesheetError("NOT_FOUND")` when the id doesn't resolve (matches
|
|
258
|
+
* both the `Record not found` GraphQL error path AND the data-shape
|
|
259
|
+
* sentinel `data.node === null`).
|
|
260
|
+
*
|
|
261
|
+
* Throws `TimesheetError("UNKNOWN")` if the node resolves but isn't a
|
|
262
|
+
* `BillingCycle` — that would be a wire surprise (a different `node`
|
|
263
|
+
* type carrying our fragment) and warrants surfacing rather than
|
|
264
|
+
* silent type-coercion.
|
|
265
|
+
*/
|
|
266
|
+
export declare function show(token: string, id: string): Promise<TimesheetDetail>;
|
|
267
|
+
/**
|
|
268
|
+
* Resolve the "current" pending timesheet — the billing cycle whose
|
|
269
|
+
* submission window contains `now` AND which is not yet submitted.
|
|
270
|
+
*
|
|
271
|
+
* - `kind: "found"`: exactly one cycle matches.
|
|
272
|
+
* - `kind: "none"`: zero cycles match (too early before next window,
|
|
273
|
+
* too late after every cycle's deadline, or no engagements have
|
|
274
|
+
* timesheets enabled).
|
|
275
|
+
* - `kind: "multiple"`: more than one cycle matches (parallel
|
|
276
|
+
* engagements with overlapping current windows).
|
|
277
|
+
*
|
|
278
|
+
* When `opts.engagement` is provided, the resolution scopes to that
|
|
279
|
+
* engagement (uses `Timesheets($jobActivityItemId)` + client-side
|
|
280
|
+
* filter to `timesheetSubmitted === false`). Otherwise uses
|
|
281
|
+
* `PendingTimesheets` (server-side filtered).
|
|
282
|
+
*
|
|
283
|
+
* The `now` option exists for deterministic testing; production code
|
|
284
|
+
* paths pass nothing and the helper uses `new Date()`.
|
|
285
|
+
*/
|
|
286
|
+
export declare function resolveCurrentCycle(token: string, opts?: ResolveCurrentCycleOptions): Promise<CurrentCycleResolution>;
|
|
287
|
+
/**
|
|
288
|
+
* Submit a timesheet for billing.
|
|
289
|
+
*
|
|
290
|
+
* **Destructive**: the submission is one-way at the wire level — once
|
|
291
|
+
* submitted, the timesheet enters Toptal's billing pipeline. Callers
|
|
292
|
+
* (CLI / MCP) are responsible for end-user confirmation.
|
|
293
|
+
*
|
|
294
|
+
* `id` is the BillingCycle.id from `list()` / `show()`. Returns the
|
|
295
|
+
* post-submission detail payload (with `timesheetSubmitted: true`)
|
|
296
|
+
* wrapped in `{ kind: "applied", result }` on the apply path.
|
|
297
|
+
*
|
|
298
|
+
* Dry-run path (`options.dryRun === true`): builds a
|
|
299
|
+
* {@link DryRunPreview} of the mutation WITHOUT invoking the
|
|
300
|
+
* gateway transport. Returns `{ kind: "preview", preview }`. The
|
|
301
|
+
* CLI's `--dry-run` flag flows through here so the destructive
|
|
302
|
+
* mutation is never sent in preview mode. See {@link DryRunOptions}
|
|
303
|
+
* for the placeholder-id semantics when the CLI is in auto-resolve
|
|
304
|
+
* mode.
|
|
305
|
+
*
|
|
306
|
+
* Throws (apply path only — dry-run never throws domain errors):
|
|
307
|
+
* - `TimesheetError("NOT_FOUND")` when the id doesn't resolve to a
|
|
308
|
+
* billing cycle the viewer can submit AND the server is willing to
|
|
309
|
+
* communicate that as a structured error (Relay-style global-id
|
|
310
|
+
* decode failure, matched via {@link NOT_FOUND_MESSAGE_PATTERN}).
|
|
311
|
+
* - `TimesheetError("GRAPHQL_ERROR")` when the server returns a
|
|
312
|
+
* top-level GraphQL error other than the Relay decode pattern.
|
|
313
|
+
* Empirically (E2E 2026-05-12), Toptal's `SubmitTimesheet` returns
|
|
314
|
+
* `"500: Internal Server Error"` for syntactically valid but
|
|
315
|
+
* non-existent BillingCycle ids — the wire does not pre-validate the
|
|
316
|
+
* id, so the 500 surfaces verbatim. The CLI presents it as
|
|
317
|
+
* `GRAPHQL_ERROR` (not `NOT_FOUND`) to avoid misleading the caller
|
|
318
|
+
* into thinking the id was definitively absent vs. the server
|
|
319
|
+
* genuinely failed.
|
|
320
|
+
* - `TimesheetError("MUTATION_ERROR")` when the server reports
|
|
321
|
+
* `success: false` on `MutationResult` (commonly: missing required
|
|
322
|
+
* hours, already submitted, deadline passed). The message carries
|
|
323
|
+
* the server-side error code+key+message tuples.
|
|
324
|
+
*/
|
|
325
|
+
export declare function submit(token: string, id: string, options?: DryRunOptions): Promise<SubmitOutcome>;
|
|
326
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/timesheet/index.ts"],"names":[],"mappings":"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"}
|