@lindle/sharepoint_requests 0.1.18 → 0.1.20

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 (56) hide show
  1. package/README.md +121 -116
  2. package/dist/index.d.mts +469 -0
  3. package/dist/index.d.ts +460 -8
  4. package/dist/index.js +892 -5
  5. package/dist/index.js.map +1 -0
  6. package/dist/index.mjs +863 -0
  7. package/dist/index.mjs.map +1 -0
  8. package/package.json +5 -5
  9. package/src/index.ts +3 -5
  10. package/src/root/index.ts +281 -146
  11. package/src/root/instance.ts +7 -0
  12. package/src/root/internal/context.ts +2 -3
  13. package/src/root/internal/digest.ts +18 -11
  14. package/src/root/internal/emailRequests.ts +5 -11
  15. package/src/root/internal/listRequests/createAttachment.ts +16 -5
  16. package/src/root/internal/listRequests/createListItem.ts +18 -27
  17. package/src/root/internal/listRequests/deleteFile.ts +5 -5
  18. package/src/root/internal/listRequests/deleteItem.ts +17 -3
  19. package/src/root/internal/listRequests/getFiles.ts +4 -10
  20. package/src/root/internal/listRequests/getListItems.ts +43 -9
  21. package/src/root/internal/listRequests/getOnly.ts +10 -7
  22. package/src/root/internal/listRequests/normalizePayload.ts +1 -1
  23. package/src/root/internal/listRequests/resolveMetadataType.ts +14 -0
  24. package/src/root/internal/listRequests/updateListItem.ts +24 -30
  25. package/src/root/internal/listRequests/uploadFile.ts +13 -21
  26. package/src/root/internal/listRequests/utils.ts +1 -1
  27. package/src/root/internal/odata.ts +2 -4
  28. package/src/root/internal/sharepointUrl.ts +7 -0
  29. package/src/root/internal/userRequests.ts +93 -37
  30. package/src/types.ts +154 -149
  31. package/dist/root/index.d.ts +0 -88
  32. package/dist/root/instance.d.ts +0 -3
  33. package/dist/root/internal/context.d.ts +0 -11
  34. package/dist/root/internal/digest.d.ts +0 -2
  35. package/dist/root/internal/emailRequests.d.ts +0 -10
  36. package/dist/root/internal/listRequests/createAttachment.d.ts +0 -3
  37. package/dist/root/internal/listRequests/createListItem.d.ts +0 -3
  38. package/dist/root/internal/listRequests/deleteFile.d.ts +0 -3
  39. package/dist/root/internal/listRequests/deleteItem.d.ts +0 -3
  40. package/dist/root/internal/listRequests/getFiles.d.ts +0 -8
  41. package/dist/root/internal/listRequests/getListItems.d.ts +0 -8
  42. package/dist/root/internal/listRequests/getOnly.d.ts +0 -3
  43. package/dist/root/internal/listRequests/index.d.ts +0 -9
  44. package/dist/root/internal/listRequests/normalizePayload.d.ts +0 -4
  45. package/dist/root/internal/listRequests/updateListItem.d.ts +0 -3
  46. package/dist/root/internal/listRequests/uploadFile.d.ts +0 -6
  47. package/dist/root/internal/listRequests/utils.d.ts +0 -2
  48. package/dist/root/internal/odata.d.ts +0 -5
  49. package/dist/root/internal/userRequests.d.ts +0 -15
  50. package/dist/sharepoint_requests.cjs.development.js +0 -1325
  51. package/dist/sharepoint_requests.cjs.development.js.map +0 -1
  52. package/dist/sharepoint_requests.cjs.production.min.js +0 -2
  53. package/dist/sharepoint_requests.cjs.production.min.js.map +0 -1
  54. package/dist/sharepoint_requests.esm.js +0 -1318
  55. package/dist/sharepoint_requests.esm.js.map +0 -1
  56. package/dist/types.d.ts +0 -256
@@ -1,44 +1,35 @@
1
- import type { ListData, SHAREPOINT_VER } from '../../../types';
1
+ import { ListData, SHAREPOINT_VER } from '../../../types';
2
2
  import { fetchDigest } from '../digest';
3
- import type { RequestContext } from '../context';
3
+ import { RequestContext } from '../context';
4
4
  import { normalizeListPayload } from './normalizePayload';
5
-
6
- async function resolveMetadataType<V extends SHAREPOINT_VER>(
7
- context: RequestContext<V>
8
- ) {
9
- if (context.resolveListItemEntityType) {
10
- return context.resolveListItemEntityType();
11
- }
12
-
13
- if (context.listItemEntityTypeFullName) {
14
- return context.listItemEntityTypeFullName;
15
- }
16
-
17
- return `SP.Data.${context.listName}ListItem`;
18
- }
5
+ import { resolveMetadataType } from './resolveMetadataType';
19
6
 
20
7
  export async function createListItem<
21
8
  D = Record<string, unknown>,
22
9
  V extends SHAREPOINT_VER = '2013'
23
10
  >(context: RequestContext<V>, listData: ListData<D>) {
24
- const metadataType = await resolveMetadataType(context);
25
11
  const normalizedData = normalizeListPayload(listData);
26
- const userMetadata =
27
- (listData as { __metadata?: Record<string, unknown> }).__metadata;
28
- const payload = {
29
- ...normalizedData,
30
- __metadata: {
31
- ...(userMetadata && typeof userMetadata === 'object' ? userMetadata : {}),
32
- type: metadataType,
33
- },
34
- };
12
+ let payload: Record<string, unknown> = normalizedData;
13
+
14
+ if (context.sharepointVersion === '2013') {
15
+ const metadataType = await resolveMetadataType(context);
16
+ const userMetadata = (listData as { __metadata?: Record<string, unknown> })
17
+ .__metadata;
18
+ payload = {
19
+ ...normalizedData,
20
+ __metadata: {
21
+ ...(userMetadata && typeof userMetadata === 'object' ? userMetadata : {}),
22
+ type: metadataType,
23
+ },
24
+ };
25
+ }
35
26
 
36
27
  const response = await context.instance({
37
28
  url: context.endpoint,
38
29
  method: 'POST',
39
30
  data: JSON.stringify(payload),
40
31
  headers: {
41
- 'X-RequestDigest': await fetchDigest(context.instance, context.baseURL),
32
+ 'X-RequestDigest': await fetchDigest(context.instance),
42
33
  },
43
34
  });
44
35
 
@@ -1,7 +1,8 @@
1
- import type { SHAREPOINT_VER, DeleteFileProps } from '../../../types';
1
+ import { SHAREPOINT_VER, DeleteFileProps } from '../../../types';
2
2
  import { fetchDigest } from '../digest';
3
- import type { RequestContext } from '../context';
3
+ import { RequestContext } from '../context';
4
4
  import { buildLibraryPath, encodeFileName } from './utils';
5
+ import { buildListByTitleEndpoint } from '../sharepointUrl';
5
6
 
6
7
  const SUPPORTED_VERSION: SHAREPOINT_VER = '2013';
7
8
 
@@ -16,11 +17,11 @@ export async function deleteFile<V extends SHAREPOINT_VER = '2013'>(
16
17
  }
17
18
 
18
19
  const encodedFileName = encodeFileName(fileName);
19
- let requestUrl = `_api/web/lists/getbytitle('${context.listName}')/RootFolder`;
20
+ let requestUrl = `${buildListByTitleEndpoint(context.listName)}/RootFolder`;
20
21
  requestUrl += buildLibraryPath(folderPath);
21
22
  requestUrl += `/Files('${encodedFileName}')`;
22
23
 
23
- const digest = await fetchDigest(context.instance, context.baseURL);
24
+ const digest = await fetchDigest(context.instance);
24
25
 
25
26
  const response = await context.instance.post(requestUrl, undefined, {
26
27
  headers: {
@@ -28,7 +29,6 @@ export async function deleteFile<V extends SHAREPOINT_VER = '2013'>(
28
29
  'IF-MATCH': '*',
29
30
  'X-RequestDigest': digest,
30
31
  },
31
- withCredentials: true,
32
32
  });
33
33
 
34
34
  return response;
@@ -1,10 +1,24 @@
1
- import type { SHAREPOINT_VER } from '../../../types';
2
- import type { RequestContext } from '../context';
1
+ import { SHAREPOINT_VER } from '../../../types';
2
+ import { RequestContext } from '../context';
3
+ import { fetchDigest } from '../digest';
4
+ import { buildListByTitleEndpoint } from '../sharepointUrl';
3
5
 
4
6
  export async function deleteItem<V extends SHAREPOINT_VER = '2013'>(
5
7
  context: RequestContext<V>,
6
8
  id: number | string
7
9
  ) {
8
- const response = await context.instance.delete(`${context.endpoint}(${id})`);
10
+ const requestDigest = await fetchDigest(context.instance);
11
+ const url =
12
+ context.sharepointVersion === '2013'
13
+ ? `${buildListByTitleEndpoint(context.listName)}/items(${id})`
14
+ : `${context.endpoint}(${id})`;
15
+
16
+ const response = await context.instance.post(url, undefined, {
17
+ headers: {
18
+ 'X-RequestDigest': requestDigest,
19
+ 'IF-MATCH': '*',
20
+ 'X-HTTP-Method': 'DELETE',
21
+ },
22
+ });
9
23
  return response;
10
24
  }
@@ -1,11 +1,8 @@
1
- import type { Folder_Options, SHAREPOINT_VER } from '../../../types';
1
+ import { Folder_Options, SHAREPOINT_VER } from '../../../types';
2
2
  import { appendParamsToUrl, buildODataParams } from '../odata';
3
- import type { RequestContext } from '../context';
3
+ import { RequestContext } from '../context';
4
4
 
5
- export async function getFiles<
6
- T,
7
- V extends SHAREPOINT_VER = '2013'
8
- >(
5
+ export async function getFiles<T, V extends SHAREPOINT_VER = '2013'>(
9
6
  context: RequestContext<V>,
10
7
  options?: Folder_Options<T>
11
8
  ) {
@@ -20,10 +17,7 @@ export async function getFiles<
20
17
  params,
21
18
  });
22
19
 
23
- const data =
24
- options?.limit || !response.data.d.results
25
- ? response.data.d
26
- : response.data.d.results;
20
+ const data = response.data?.d?.results ?? response.data?.d ?? response.data;
27
21
 
28
22
  const meta = { url };
29
23
 
@@ -1,14 +1,38 @@
1
- import type { Options, SPItem, SHAREPOINT_VER } from '../../../types';
1
+ import { Options, SPItem, SHAREPOINT_VER } from '../../../types';
2
2
  import { appendParamsToUrl, buildODataParams } from '../odata';
3
- import type { RequestContext } from '../context';
3
+ import { RequestContext } from '../context';
4
+
5
+ type ListItemShape<K, V extends SHAREPOINT_VER> = K & Partial<SPItem<V>>;
6
+
7
+ type SelectedColumns<
8
+ K,
9
+ V extends SHAREPOINT_VER,
10
+ O extends Options<K, V> | undefined
11
+ > = O extends { cols?: infer C }
12
+ ? C extends readonly (infer Key)[]
13
+ ? Extract<Key, keyof ListItemShape<K, V>>
14
+ : Extract<C, keyof ListItemShape<K, V>>
15
+ : never;
16
+
17
+ type ListItemResponseData<
18
+ K,
19
+ V extends SHAREPOINT_VER,
20
+ O extends Options<K, V> | undefined
21
+ > = [SelectedColumns<K, V, O>] extends [never]
22
+ ? ListItemShape<K, V>
23
+ : Pick<ListItemShape<K, V>, SelectedColumns<K, V, O>>;
4
24
 
5
25
  export async function getListItems<
6
26
  K,
7
- V extends SHAREPOINT_VER = '2013'
27
+ V extends SHAREPOINT_VER = '2013',
28
+ O extends Options<K, V> | undefined = Options<K, V> | undefined
8
29
  >(
9
30
  context: RequestContext<V>,
10
- options?: Options<K, V>
11
- ): Promise<{ data: (K & Partial<SPItem<V>>)[]; meta: { url: URL } }> {
31
+ options?: O
32
+ ): Promise<{
33
+ data: ListItemResponseData<K, V, O> | ListItemResponseData<K, V, O>[] | undefined;
34
+ meta: { url: URL };
35
+ }> {
12
36
  const params = buildODataParams(options, ' ');
13
37
 
14
38
  const url = new URL(context.endpoint, context.instance.defaults.baseURL);
@@ -20,10 +44,14 @@ export async function getListItems<
20
44
  params,
21
45
  });
22
46
 
23
- const rawData = response.data?.d?.results ?? response.data?.d ?? response.data;
24
- const cols = options?.cols;
47
+ const rawData =
48
+ response.data?.d?.results ?? response.data?.d ?? response.data;
49
+ const cols = options?.cols as Options<K, V>['cols'] | undefined;
25
50
  const keys = cols
26
- ? (Array.isArray(cols) ? cols : [cols]).filter(Boolean)
51
+ ? (Array.isArray(cols) ? cols : [cols]).filter(
52
+ (key): key is Extract<keyof ListItemShape<K, V>, string> =>
53
+ typeof key === 'string'
54
+ )
27
55
  : undefined;
28
56
 
29
57
  const filterItem = (item: unknown) => {
@@ -46,5 +74,11 @@ export async function getListItems<
46
74
 
47
75
  const meta = { url };
48
76
 
49
- return { data: data as (K & Partial<SPItem<V>>)[], meta };
77
+ return {
78
+ data: data as
79
+ | ListItemResponseData<K, V, O>
80
+ | ListItemResponseData<K, V, O>[]
81
+ | undefined,
82
+ meta,
83
+ };
50
84
  }
@@ -1,18 +1,21 @@
1
- import type { AxiosInstance } from 'axios';
2
- import type { Options, SHAREPOINT_VER } from '../../../types';
1
+ import { AxiosInstance } from 'axios';
2
+ import { Options, SHAREPOINT_VER } from '../../../types';
3
3
  import { buildODataParams } from '../odata';
4
4
 
5
- export async function getOnly<
6
- T,
7
- V extends SHAREPOINT_VER = '2013'
8
- >(
5
+ export async function getOnly<T, V extends SHAREPOINT_VER = '2013'>(
9
6
  instance: AxiosInstance,
10
7
  endpoint: string,
11
8
  options?: Options<T, V>
12
9
  ) {
13
10
  const params = buildODataParams(options, ',');
14
11
  const { data } = await instance.get(endpoint, { params });
15
- if (data) {
12
+ if (!data) {
13
+ return undefined;
14
+ }
15
+
16
+ if (data.d) {
16
17
  return data.d.results ? data.d.results : data.d;
17
18
  }
19
+
20
+ return data;
18
21
  }
@@ -1,4 +1,4 @@
1
- import type { ListData } from '../../../types';
1
+ import { ListData } from '../../../types';
2
2
 
3
3
  type AnyRecord = Record<string, unknown>;
4
4
 
@@ -0,0 +1,14 @@
1
+ import { SHAREPOINT_VER } from '../../../types';
2
+ import { RequestContext } from '../context';
3
+
4
+ export async function resolveMetadataType<V extends SHAREPOINT_VER>(
5
+ context: RequestContext<V>
6
+ ): Promise<string> {
7
+ if (context.resolveListItemEntityType) {
8
+ return context.resolveListItemEntityType();
9
+ }
10
+ if (context.listItemEntityTypeFullName) {
11
+ return context.listItemEntityTypeFullName;
12
+ }
13
+ return `SP.Data.${context.listName}ListItem`;
14
+ }
@@ -1,21 +1,9 @@
1
- import type { ListData, SHAREPOINT_VER } from '../../../types';
1
+ import { ListData, SHAREPOINT_VER } from '../../../types';
2
2
  import { fetchDigest } from '../digest';
3
- import type { RequestContext } from '../context';
3
+ import { RequestContext } from '../context';
4
4
  import { normalizeListPayload } from './normalizePayload';
5
-
6
- async function resolveMetadataType<V extends SHAREPOINT_VER>(
7
- context: RequestContext<V>
8
- ) {
9
- if (context.resolveListItemEntityType) {
10
- return context.resolveListItemEntityType();
11
- }
12
-
13
- if (context.listItemEntityTypeFullName) {
14
- return context.listItemEntityTypeFullName;
15
- }
16
-
17
- return `SP.Data.${context.listName}ListItem`;
18
- }
5
+ import { buildListByTitleEndpoint } from '../sharepointUrl';
6
+ import { resolveMetadataType } from './resolveMetadataType';
19
7
 
20
8
  export async function updateListItem<
21
9
  D = Record<string, unknown>,
@@ -26,22 +14,28 @@ export async function updateListItem<
26
14
  data: ListData<D>,
27
15
  digest?: string
28
16
  ) {
29
- const formDigestValue =
30
- digest || (await fetchDigest(context.instance, context.baseURL));
17
+ const formDigestValue = digest ?? (await fetchDigest(context.instance));
31
18
 
32
- const metadataType = await resolveMetadataType(context);
33
19
  const normalizedData = normalizeListPayload(data);
34
- const userMetadata = (data as { __metadata?: Record<string, unknown> })
35
- .__metadata;
36
- const payload = {
37
- ...normalizedData,
38
- __metadata: {
39
- ...(userMetadata && typeof userMetadata === 'object' ? userMetadata : {}),
40
- type: metadataType,
41
- },
42
- };
20
+ let payload: Record<string, unknown> = normalizedData;
21
+
22
+ if (context.sharepointVersion === '2013') {
23
+ const metadataType = await resolveMetadataType(context);
24
+ const userMetadata = (data as { __metadata?: Record<string, unknown> })
25
+ .__metadata;
26
+ payload = {
27
+ ...normalizedData,
28
+ __metadata: {
29
+ ...(userMetadata && typeof userMetadata === 'object' ? userMetadata : {}),
30
+ type: metadataType,
31
+ },
32
+ };
33
+ }
43
34
 
44
- const url = `_api/lists/getbytitle('${context.listName}')/getItemById('${id}')`;
35
+ const url =
36
+ context.sharepointVersion === '2013'
37
+ ? `${buildListByTitleEndpoint(context.listName)}/items(${id})`
38
+ : `${context.endpoint}(${id})`;
45
39
 
46
40
  const response = await context.instance({
47
41
  url,
@@ -50,7 +44,7 @@ export async function updateListItem<
50
44
  headers: {
51
45
  'X-RequestDigest': formDigestValue,
52
46
  'IF-MATCH': '*',
53
- 'X-Http-Method': 'PATCH',
47
+ 'X-HTTP-Method': 'MERGE',
54
48
  },
55
49
  });
56
50
 
@@ -1,19 +1,16 @@
1
- import type { AxiosResponse } from 'axios';
2
- import type {
3
- ItemID,
4
- SHAREPOINT_VER,
5
- UploadFileProps,
6
- } from '../../../types';
1
+ import { AxiosResponse } from 'axios';
2
+ import { ItemID, SHAREPOINT_VER, UploadFileProps } from '../../../types';
7
3
  import { fetchDigest } from '../digest';
8
- import type { RequestContext } from '../context';
4
+ import { RequestContext } from '../context';
9
5
  import { buildLibraryPath, encodeFileName } from './utils';
6
+ import { buildListByTitleEndpoint } from '../sharepointUrl';
10
7
 
11
8
  const SUPPORTED_VERSION: SHAREPOINT_VER = '2013';
12
9
 
13
10
  export async function uploadFile<V extends SHAREPOINT_VER = '2013'>(
14
11
  context: RequestContext<V>,
15
12
  { file, folderPath, overwrite = true }: UploadFileProps
16
- ): Promise<AxiosResponse & { itemId?: ItemID }> {
13
+ ): Promise<{ response: AxiosResponse; itemId?: ItemID }> {
17
14
  if (context.sharepointVersion !== SUPPORTED_VERSION) {
18
15
  throw new Error(
19
16
  'uploadFile is only supported for SharePoint 2013 REST endpoints.'
@@ -24,8 +21,7 @@ export async function uploadFile<V extends SHAREPOINT_VER = '2013'>(
24
21
  throw new Error('A file with a valid name must be provided.');
25
22
  }
26
23
 
27
- let requestUrl = `_api/web/lists/getbytitle('${context.listName}')/RootFolder`;
28
-
24
+ let requestUrl = `${buildListByTitleEndpoint(context.listName)}/RootFolder`;
29
25
  requestUrl += buildLibraryPath(folderPath);
30
26
 
31
27
  const encodedFileName = encodeFileName(file.name);
@@ -33,37 +29,33 @@ export async function uploadFile<V extends SHAREPOINT_VER = '2013'>(
33
29
  overwrite ? 'true' : 'false'
34
30
  })?$expand=ListItemAllFields&$select=ListItemAllFields/Id`;
35
31
 
36
- const digest = await fetchDigest(context.instance, context.baseURL);
32
+ const digest = await fetchDigest(context.instance);
37
33
 
38
- const response = (await context.instance.post(requestUrl, file, {
34
+ const response = await context.instance.post(requestUrl, file, {
39
35
  headers: {
40
36
  'Content-Type': file.type || 'application/octet-stream',
41
37
  'X-RequestDigest': digest,
42
38
  },
43
- withCredentials: true,
44
- })) as AxiosResponse & { itemId?: ItemID };
39
+ });
45
40
 
46
41
  const responseData = response.data?.d ?? response.data;
47
42
  const listItemFields = responseData?.ListItemAllFields;
48
- const itemId =
43
+ let itemId: ItemID | undefined =
49
44
  listItemFields?.Id ??
50
45
  listItemFields?.ID ??
51
46
  responseData?.Id ??
52
47
  responseData?.ID;
53
48
 
54
- if (typeof itemId !== 'undefined') {
55
- response.itemId = itemId;
56
- } else if (listItemFields?.__deferred?.uri) {
49
+ if (typeof itemId === 'undefined' && listItemFields?.__deferred?.uri) {
57
50
  const listItemResponse = await context.instance.get(
58
51
  listItemFields.__deferred.uri
59
52
  );
60
53
  const deferredId =
61
54
  listItemResponse.data?.d?.Id ?? listItemResponse.data?.Id;
62
-
63
55
  if (typeof deferredId !== 'undefined') {
64
- response.itemId = deferredId;
56
+ itemId = deferredId;
65
57
  }
66
58
  }
67
59
 
68
- return response;
60
+ return { response, itemId };
69
61
  }
@@ -11,7 +11,7 @@ export function buildLibraryPath(folderPath?: string | string[]) {
11
11
  return '';
12
12
  }
13
13
 
14
- const escapedSegments = segments.map((segment) => {
14
+ const escapedSegments = segments.map(segment => {
15
15
  if (!segment.trim()) {
16
16
  throw new Error('Folder path segments cannot be empty.');
17
17
  }
@@ -1,4 +1,4 @@
1
- import type { Folder_Options, Options, SHAREPOINT_VER } from '../../types';
1
+ import { Folder_Options, Options, SHAREPOINT_VER } from '../../types';
2
2
 
3
3
  type QueryableOptions<T, V extends SHAREPOINT_VER = '2013'> =
4
4
  | Options<T, V>
@@ -16,9 +16,7 @@ export function buildODataParams<T, V extends SHAREPOINT_VER = '2013'>(
16
16
  $skip: skip,
17
17
  $select: Array.isArray(cols) ? cols.join(',') : cols,
18
18
  $filter: filter,
19
- $orderby: Array.isArray(orderBy)
20
- ? orderBy.join(orderBySeparator)
21
- : orderBy,
19
+ $orderby: Array.isArray(orderBy) ? orderBy.join(orderBySeparator) : orderBy,
22
20
  };
23
21
 
24
22
  return params;
@@ -0,0 +1,7 @@
1
+ export function escapeODataStringLiteral(value: string) {
2
+ return value.replace(/'/g, "''");
3
+ }
4
+
5
+ export function buildListByTitleEndpoint(listName: string) {
6
+ return `_api/web/lists/GetByTitle('${escapeODataStringLiteral(listName)}')`;
7
+ }
@@ -1,17 +1,28 @@
1
- import type { AxiosInstance } from 'axios';
2
- import type {
3
- PersonField,
4
- UserPermision,
5
- UserProfile,
6
- } from '../../types';
7
- import type { RequestContext } from './context';
1
+ import { AxiosInstance } from 'axios';
2
+ import { PermissionLabel, PersonField, UserPermision, UserProfile } from '../../types';
3
+ import { RequestContext } from './context';
4
+ import { escapeODataStringLiteral } from './sharepointUrl';
8
5
 
9
6
  type RoleDefinition = {
7
+ Name: string;
8
+ Description: string;
9
+ Hidden: boolean;
10
+ Id: number;
11
+ Order: number;
12
+ RoleTypeKind: number;
10
13
  BasePermissions: {
11
14
  High: string;
15
+ Low: string;
12
16
  };
13
17
  };
14
18
 
19
+ const PERMISSION_PRIORITY: PermissionLabel[] = [
20
+ 'Full Control',
21
+ 'Edit',
22
+ 'Contribute',
23
+ 'Read',
24
+ ];
25
+
15
26
  export async function typeAhead(
16
27
  instance: AxiosInstance,
17
28
  input: string,
@@ -46,64 +57,109 @@ export async function typeAhead(
46
57
  },
47
58
  });
48
59
 
49
- return response.data.d.ClientPeoplePickerSearchUser;
60
+ const searchResults =
61
+ response.data?.d?.ClientPeoplePickerSearchUser ??
62
+ response.data?.ClientPeoplePickerSearchUser;
63
+
64
+ if (typeof searchResults === 'string') {
65
+ const parsed = JSON.parse(searchResults);
66
+ if (Array.isArray(parsed) && parsed.length > 0) {
67
+ return parsed[0] as PersonField;
68
+ }
69
+ throw new Error('People picker returned no results.');
70
+ }
71
+
72
+ if (Array.isArray(searchResults) && searchResults.length > 0) {
73
+ return searchResults[0] as PersonField;
74
+ }
75
+
76
+ if (searchResults && typeof searchResults === 'object') {
77
+ return searchResults as PersonField;
78
+ }
79
+
80
+ throw new Error('Invalid people picker response.');
50
81
  }
51
82
 
52
83
  export async function currentUserProperties(
53
84
  instance: AxiosInstance
54
85
  ): Promise<UserProfile> {
55
86
  const requestUrl = '_api/sp.userprofiles.peoplemanager/getmyproperties';
56
- let profile: any = await instance.get(requestUrl);
87
+ const response = await instance.get(requestUrl);
57
88
 
58
- profile = profile.data.d;
89
+ const profile = (response.data?.d ?? response.data) as UserProfile | undefined;
90
+ if (!profile) {
91
+ throw new Error('Unable to retrieve user profile.');
92
+ }
59
93
 
60
- profile.UserProfileProperties.results.forEach(
94
+ profile.UserProfileProperties?.results?.forEach(
61
95
  (prop: { Key: string; Value: string }) => {
62
- if (prop.Key === 'FirstName') {
63
- profile.FirstName = prop.Value;
64
- }
65
- if (prop.Key === 'LastName') {
66
- profile.LastName = prop.Value;
67
- }
68
- if (prop.Key === 'Country') {
69
- profile.Country = prop.Value;
70
- }
71
- if (prop.Key === 'UserName') {
72
- profile.UserName = prop.Value;
73
- }
96
+ if (prop.Key === 'FirstName') profile.FirstName = prop.Value;
97
+ else if (prop.Key === 'LastName') profile.LastName = prop.Value;
98
+ else if (prop.Key === 'Country') profile.Country = prop.Value;
99
+ else if (prop.Key === 'UserName') profile.UserName = prop.Value;
74
100
  }
75
101
  );
76
102
 
77
103
  return profile;
78
104
  }
79
105
 
80
- export async function getSiteUser(instance: AxiosInstance, searchValue: string) {
81
- const requestUrl = `_api/web/siteusers?&$filter=substringof('${searchValue}', Title) or substringof('${searchValue}', LoginName) or substringof('${searchValue}', Email)&$top=50&$expand=Groups`;
82
- const response = await instance.get(requestUrl);
106
+ export async function getSiteUser(
107
+ instance: AxiosInstance,
108
+ searchValue: string
109
+ ) {
110
+ const safeSearchValue = escapeODataStringLiteral(searchValue);
111
+ const response = await instance.get('_api/web/siteusers', {
112
+ params: {
113
+ $filter: `substringof('${safeSearchValue}', Title) or substringof('${safeSearchValue}', LoginName) or substringof('${safeSearchValue}', Email)`,
114
+ $top: 50,
115
+ $expand: 'Groups',
116
+ },
117
+ });
83
118
  return response.data.d;
84
119
  }
85
120
 
86
- export async function roleDefinitions(instance: AxiosInstance) {
87
- const requestUrl = `_api/Web/RoleDefinitions`;
88
- const response = await instance.get(requestUrl);
121
+ async function roleDefinitions(instance: AxiosInstance): Promise<RoleDefinition[]> {
122
+ const response = await instance.get('_api/Web/RoleDefinitions');
89
123
  return response.data.d.results as RoleDefinition[];
90
124
  }
91
125
 
92
- export async function getEffectiveBasePermissions(instance: AxiosInstance) {
93
- const requestUrl = `_api/Web/effectiveBasePermissions`;
94
- const response = await instance.get(requestUrl);
126
+ async function getEffectiveBasePermissions(
127
+ instance: AxiosInstance
128
+ ): Promise<{ High: string; Low: string }> {
129
+ const response = await instance.get('_api/Web/effectiveBasePermissions');
95
130
  return response.data.d.EffectiveBasePermissions;
96
131
  }
97
132
 
98
133
  export async function currentUserPermissions(
99
134
  context: RequestContext
100
135
  ): Promise<UserPermision> {
101
- const { High } = await getEffectiveBasePermissions(context.instance);
136
+ const { High: userHigh, Low: userLow } = await getEffectiveBasePermissions(
137
+ context.instance
138
+ );
102
139
  const definitions = await roleDefinitions(context.instance);
103
140
 
104
- const result = definitions.find(
105
- ({ BasePermissions }: RoleDefinition) => BasePermissions.High === High
106
- );
141
+ const userHighInt = parseInt(userHigh, 10);
142
+ const userLowInt = parseInt(userLow, 10);
143
+
144
+ const matching = definitions
145
+ .filter((def): def is RoleDefinition =>
146
+ PERMISSION_PRIORITY.includes(def.Name as PermissionLabel) &&
147
+ (userHighInt & parseInt(def.BasePermissions.High, 10)) === parseInt(def.BasePermissions.High, 10) &&
148
+ (userLowInt & parseInt(def.BasePermissions.Low, 10)) === parseInt(def.BasePermissions.Low, 10)
149
+ )
150
+ .sort(
151
+ (a, b) =>
152
+ PERMISSION_PRIORITY.indexOf(a.Name as PermissionLabel) -
153
+ PERMISSION_PRIORITY.indexOf(b.Name as PermissionLabel)
154
+ );
155
+
156
+ const result = matching[0];
157
+
158
+ if (!result) {
159
+ throw new Error(
160
+ `Unable to resolve current user permission. Effective: High="${userHigh}", Low="${userLow}"`
161
+ );
162
+ }
107
163
 
108
- return result as UserPermision;
164
+ return result as unknown as UserPermision;
109
165
  }