@lindle/sharepoint_requests 0.1.17 → 0.1.19

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 (47) hide show
  1. package/README.md +121 -116
  2. package/dist/index.d.ts +2 -2
  3. package/dist/root/index.d.ts +106 -31
  4. package/dist/root/internal/context.d.ts +2 -2
  5. package/dist/root/internal/digest.d.ts +1 -1
  6. package/dist/root/internal/emailRequests.d.ts +3 -3
  7. package/dist/root/internal/listRequests/createAttachment.d.ts +2 -2
  8. package/dist/root/internal/listRequests/createListItem.d.ts +3 -3
  9. package/dist/root/internal/listRequests/deleteFile.d.ts +2 -2
  10. package/dist/root/internal/listRequests/deleteItem.d.ts +2 -2
  11. package/dist/root/internal/listRequests/getFiles.d.ts +2 -2
  12. package/dist/root/internal/listRequests/getListItems.d.ts +3 -3
  13. package/dist/root/internal/listRequests/getOnly.d.ts +2 -2
  14. package/dist/root/internal/listRequests/normalizePayload.d.ts +4 -0
  15. package/dist/root/internal/listRequests/updateListItem.d.ts +3 -3
  16. package/dist/root/internal/listRequests/uploadFile.d.ts +3 -3
  17. package/dist/root/internal/odata.d.ts +1 -1
  18. package/dist/root/internal/sharepointUrl.d.ts +2 -0
  19. package/dist/root/internal/userRequests.d.ts +3 -3
  20. package/dist/sharepoint_requests.cjs.development.js +572 -229
  21. package/dist/sharepoint_requests.cjs.development.js.map +1 -1
  22. package/dist/sharepoint_requests.cjs.production.min.js +1 -1
  23. package/dist/sharepoint_requests.cjs.production.min.js.map +1 -1
  24. package/dist/sharepoint_requests.esm.js +572 -229
  25. package/dist/sharepoint_requests.esm.js.map +1 -1
  26. package/dist/types.d.ts +30 -11
  27. package/package.json +1 -1
  28. package/src/index.ts +3 -5
  29. package/src/root/index.ts +263 -144
  30. package/src/root/internal/context.ts +2 -2
  31. package/src/root/internal/digest.ts +4 -3
  32. package/src/root/internal/emailRequests.ts +4 -7
  33. package/src/root/internal/listRequests/createAttachment.ts +14 -3
  34. package/src/root/internal/listRequests/createListItem.ts +24 -13
  35. package/src/root/internal/listRequests/deleteFile.ts +4 -3
  36. package/src/root/internal/listRequests/deleteItem.ts +17 -3
  37. package/src/root/internal/listRequests/getFiles.ts +4 -10
  38. package/src/root/internal/listRequests/getListItems.ts +16 -9
  39. package/src/root/internal/listRequests/getOnly.ts +10 -7
  40. package/src/root/internal/listRequests/normalizePayload.ts +165 -0
  41. package/src/root/internal/listRequests/updateListItem.ts +31 -13
  42. package/src/root/internal/listRequests/uploadFile.ts +5 -8
  43. package/src/root/internal/listRequests/utils.ts +1 -1
  44. package/src/root/internal/odata.ts +2 -4
  45. package/src/root/internal/sharepointUrl.ts +7 -0
  46. package/src/root/internal/userRequests.ts +43 -11
  47. package/src/types.ts +138 -105
@@ -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, context.baseURL);
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,14 @@
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
4
 
5
- export async function getListItems<
6
- K,
7
- V extends SHAREPOINT_VER = '2013'
8
- >(
5
+ export async function getListItems<K, V extends SHAREPOINT_VER = '2013'>(
9
6
  context: RequestContext<V>,
10
7
  options?: Options<K, V>
11
- ): Promise<{ data: (K & Partial<SPItem<V>>)[]; meta: { url: URL } }> {
8
+ ): Promise<{
9
+ data: (K & Partial<SPItem<V>>) | (K & Partial<SPItem<V>>)[] | undefined;
10
+ meta: { url: URL };
11
+ }> {
12
12
  const params = buildODataParams(options, ' ');
13
13
 
14
14
  const url = new URL(context.endpoint, context.instance.defaults.baseURL);
@@ -20,7 +20,8 @@ export async function getListItems<
20
20
  params,
21
21
  });
22
22
 
23
- const rawData = response.data?.d?.results ?? response.data?.d ?? response.data;
23
+ const rawData =
24
+ response.data?.d?.results ?? response.data?.d ?? response.data;
24
25
  const cols = options?.cols;
25
26
  const keys = cols
26
27
  ? (Array.isArray(cols) ? cols : [cols]).filter(Boolean)
@@ -46,5 +47,11 @@ export async function getListItems<
46
47
 
47
48
  const meta = { url };
48
49
 
49
- return { data: data as (K & Partial<SPItem<V>>)[], meta };
50
+ return {
51
+ data: data as
52
+ | (K & Partial<SPItem<V>>)
53
+ | (K & Partial<SPItem<V>>)[]
54
+ | undefined,
55
+ meta,
56
+ };
50
57
  }
@@ -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
  }
@@ -0,0 +1,165 @@
1
+ import { ListData } from '../../../types';
2
+
3
+ type AnyRecord = Record<string, unknown>;
4
+
5
+ const LOOKUP_ID_KEYS = [
6
+ 'lookupId',
7
+ 'LookupId',
8
+ 'lookupID',
9
+ 'LookupID',
10
+ 'Id',
11
+ 'ID',
12
+ ];
13
+
14
+ function isPlainObject(value: unknown): value is AnyRecord {
15
+ return !!value && typeof value === 'object' && !Array.isArray(value);
16
+ }
17
+
18
+ function extractLookupPrimitive(
19
+ value: AnyRecord,
20
+ allowGenericId: boolean
21
+ ): unknown | undefined {
22
+ for (const key of LOOKUP_ID_KEYS) {
23
+ if (!allowGenericId && (key === 'Id' || key === 'ID')) {
24
+ continue;
25
+ }
26
+ if (value[key] !== undefined && value[key] !== null) {
27
+ return value[key];
28
+ }
29
+ }
30
+ return undefined;
31
+ }
32
+
33
+ function normalizeLookupValue(value: unknown): unknown {
34
+ if (Array.isArray(value)) {
35
+ return { results: value };
36
+ }
37
+
38
+ if (isPlainObject(value)) {
39
+ if (Array.isArray(value.results)) {
40
+ return { results: value.results };
41
+ }
42
+
43
+ const primitive = extractLookupPrimitive(value, true);
44
+ if (primitive !== undefined) {
45
+ return primitive;
46
+ }
47
+ }
48
+
49
+ if (typeof value === 'string') {
50
+ const trimmed = value.trim();
51
+ if (!trimmed) {
52
+ return value;
53
+ }
54
+ const numeric = Number(trimmed);
55
+ return Number.isNaN(numeric) ? trimmed : numeric;
56
+ }
57
+
58
+ return value;
59
+ }
60
+
61
+ function resolveLookupField(key: string, value: unknown) {
62
+ const lower = key.toLowerCase();
63
+ const endsWithId = lower.endsWith('id');
64
+ const endsWithRef = lower.endsWith('ref');
65
+ const endsWithLookup = lower.endsWith('lookup');
66
+ const hasLookupHint = endsWithId || endsWithRef || endsWithLookup;
67
+
68
+ if (value === undefined) {
69
+ return null;
70
+ }
71
+
72
+ if (Array.isArray(value)) {
73
+ const targetKey = endsWithId ? key : `${key}Id`;
74
+ return {
75
+ key: targetKey,
76
+ value: { results: value },
77
+ };
78
+ }
79
+
80
+ if (isPlainObject(value)) {
81
+ const primitive = extractLookupPrimitive(value, hasLookupHint);
82
+ if (primitive !== undefined) {
83
+ const targetKey = endsWithId ? key : `${key}Id`;
84
+ return {
85
+ key: targetKey,
86
+ value: normalizeLookupValue(primitive),
87
+ };
88
+ }
89
+
90
+ if (Array.isArray(value.results)) {
91
+ const targetKey = endsWithId ? key : `${key}Id`;
92
+ return {
93
+ key: targetKey,
94
+ value: { results: value.results },
95
+ };
96
+ }
97
+
98
+ if (!hasLookupHint) {
99
+ return null;
100
+ }
101
+ } else if (!hasLookupHint) {
102
+ return null;
103
+ }
104
+
105
+ const targetKey = endsWithId ? key : `${key}Id`;
106
+
107
+ if (value === null) {
108
+ return {
109
+ key: targetKey,
110
+ value: null,
111
+ };
112
+ }
113
+
114
+ if (typeof value === 'number') {
115
+ return {
116
+ key: targetKey,
117
+ value,
118
+ };
119
+ }
120
+
121
+ if (typeof value === 'string') {
122
+ const trimmed = value.trim();
123
+ if (!trimmed) {
124
+ return {
125
+ key: targetKey,
126
+ value: '',
127
+ };
128
+ }
129
+ const numeric = Number(trimmed);
130
+ return {
131
+ key: targetKey,
132
+ value: Number.isNaN(numeric) ? trimmed : numeric,
133
+ };
134
+ }
135
+
136
+ return null;
137
+ }
138
+
139
+ export function normalizeListPayload<D = Record<string, unknown>>(
140
+ data: ListData<D>
141
+ ): AnyRecord {
142
+ const result: AnyRecord = {};
143
+
144
+ if (!data) {
145
+ return result;
146
+ }
147
+
148
+ for (const [key, value] of Object.entries(data as AnyRecord)) {
149
+ if (key === '__metadata') {
150
+ continue;
151
+ }
152
+
153
+ const lookupField = resolveLookupField(key, value);
154
+ if (lookupField) {
155
+ result[lookupField.key] = lookupField.value;
156
+ continue;
157
+ }
158
+
159
+ if (value !== undefined) {
160
+ result[key] = value;
161
+ }
162
+ }
163
+
164
+ return result;
165
+ }
@@ -1,6 +1,8 @@
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
+ import { normalizeListPayload } from './normalizePayload';
5
+ import { buildListByTitleEndpoint } from '../sharepointUrl';
4
6
 
5
7
  async function resolveMetadataType<V extends SHAREPOINT_VER>(
6
8
  context: RequestContext<V>
@@ -16,24 +18,40 @@ async function resolveMetadataType<V extends SHAREPOINT_VER>(
16
18
  return `SP.Data.${context.listName}ListItem`;
17
19
  }
18
20
 
19
- export async function updateListItem<V extends SHAREPOINT_VER = '2013'>(
21
+ export async function updateListItem<
22
+ D = Record<string, unknown>,
23
+ V extends SHAREPOINT_VER = '2013'
24
+ >(
20
25
  context: RequestContext<V>,
21
26
  id: number | string,
22
- data: ListData,
27
+ data: ListData<D>,
23
28
  digest?: string
24
29
  ) {
25
30
  const formDigestValue =
26
31
  digest || (await fetchDigest(context.instance, context.baseURL));
27
32
 
28
- const metadataType = await resolveMetadataType(context);
29
- const payload = {
30
- ...data,
31
- __metadata: {
32
- type: metadataType,
33
- },
34
- };
33
+ const normalizedData = normalizeListPayload(data);
34
+ let payload: Record<string, unknown> = normalizedData;
35
+
36
+ if (context.sharepointVersion === '2013') {
37
+ const metadataType = await resolveMetadataType(context);
38
+ const userMetadata = (data as { __metadata?: Record<string, unknown> })
39
+ .__metadata;
40
+ payload = {
41
+ ...normalizedData,
42
+ __metadata: {
43
+ ...(userMetadata && typeof userMetadata === 'object'
44
+ ? userMetadata
45
+ : {}),
46
+ type: metadataType,
47
+ },
48
+ };
49
+ }
35
50
 
36
- const url = `_api/lists/getbytitle('${context.listName}')/getItemById('${id}')`;
51
+ const url =
52
+ context.sharepointVersion === '2013'
53
+ ? `${buildListByTitleEndpoint(context.listName)}/items(${id})`
54
+ : `${context.endpoint}(${id})`;
37
55
 
38
56
  const response = await context.instance({
39
57
  url,
@@ -42,7 +60,7 @@ export async function updateListItem<V extends SHAREPOINT_VER = '2013'>(
42
60
  headers: {
43
61
  'X-RequestDigest': formDigestValue,
44
62
  'IF-MATCH': '*',
45
- 'X-Http-Method': 'PATCH',
63
+ 'X-HTTP-Method': 'MERGE',
46
64
  },
47
65
  });
48
66
 
@@ -1,12 +1,9 @@
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
 
@@ -24,7 +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`;
24
+ let requestUrl = `${buildListByTitleEndpoint(context.listName)}/RootFolder`;
28
25
 
29
26
  requestUrl += buildLibraryPath(folderPath);
30
27
 
@@ -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,10 +1,7 @@
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 { PersonField, UserPermision, UserProfile } from '../../types';
3
+ import { RequestContext } from './context';
4
+ import { escapeODataStringLiteral } from './sharepointUrl';
8
5
 
9
6
  type RoleDefinition = {
10
7
  BasePermissions: {
@@ -46,7 +43,27 @@ export async function typeAhead(
46
43
  },
47
44
  });
48
45
 
49
- return response.data.d.ClientPeoplePickerSearchUser;
46
+ const searchResults =
47
+ response.data?.d?.ClientPeoplePickerSearchUser ??
48
+ response.data?.ClientPeoplePickerSearchUser;
49
+
50
+ if (typeof searchResults === 'string') {
51
+ const parsed = JSON.parse(searchResults);
52
+ if (Array.isArray(parsed) && parsed.length > 0) {
53
+ return parsed[0] as PersonField;
54
+ }
55
+ throw new Error('People picker returned no results.');
56
+ }
57
+
58
+ if (Array.isArray(searchResults) && searchResults.length > 0) {
59
+ return searchResults[0] as PersonField;
60
+ }
61
+
62
+ if (searchResults && typeof searchResults === 'object') {
63
+ return searchResults as PersonField;
64
+ }
65
+
66
+ throw new Error('Invalid people picker response.');
50
67
  }
51
68
 
52
69
  export async function currentUserProperties(
@@ -77,9 +94,18 @@ export async function currentUserProperties(
77
94
  return profile;
78
95
  }
79
96
 
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);
97
+ export async function getSiteUser(
98
+ instance: AxiosInstance,
99
+ searchValue: string
100
+ ) {
101
+ const safeSearchValue = escapeODataStringLiteral(searchValue);
102
+ const response = await instance.get('_api/web/siteusers', {
103
+ params: {
104
+ $filter: `substringof('${safeSearchValue}', Title) or substringof('${safeSearchValue}', LoginName) or substringof('${safeSearchValue}', Email)`,
105
+ $top: 50,
106
+ $expand: 'Groups',
107
+ },
108
+ });
83
109
  return response.data.d;
84
110
  }
85
111
 
@@ -105,5 +131,11 @@ export async function currentUserPermissions(
105
131
  ({ BasePermissions }: RoleDefinition) => BasePermissions.High === High
106
132
  );
107
133
 
134
+ if (!result) {
135
+ throw new Error(
136
+ `Unable to resolve current user permission for BasePermissions.High="${High}".`
137
+ );
138
+ }
139
+
108
140
  return result as UserPermision;
109
141
  }