@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
package/src/root/index.ts CHANGED
@@ -2,41 +2,64 @@ import { AxiosInstance, CreateAxiosDefaults as AxiosConfig } from 'axios';
2
2
  import instance from './instance';
3
3
  import {
4
4
  CreateFileProps,
5
- DeleteFileProps,
6
5
  CurrentUser,
6
+ DeleteFileProps,
7
7
  EmailProps,
8
8
  Field_Options,
9
9
  Folder_Options,
10
- UploadFileProps,
11
- IListName,
12
10
  ItemID,
13
11
  ListData,
14
12
  Options,
15
13
  PersonField,
16
14
  SHAREPOINT_VER,
15
+ UploadFileProps,
17
16
  UserPermision,
18
17
  UserProfile,
19
18
  } from '../types';
20
19
  import { RequestContext } from './internal/context';
20
+ import { sendEmail as sendEmailRequest } from './internal/emailRequests';
21
+ import { fetchDigest } from './internal/digest';
21
22
  import {
22
23
  createAttachment,
23
24
  createListItem,
24
- deleteItem,
25
25
  deleteFile as deleteLibraryFile,
26
+ deleteItem,
26
27
  getFiles,
27
28
  getListItems,
28
29
  getOnly,
29
30
  updateListItem,
30
31
  uploadFile,
31
32
  } from './internal/listRequests';
32
- import { sendEmail as sendEmailRequest } from './internal/emailRequests';
33
+ import {
34
+ buildListByTitleEndpoint,
35
+ escapeODataStringLiteral,
36
+ } from './internal/sharepointUrl';
33
37
  import {
34
38
  currentUserPermissions as resolveCurrentUserPermissions,
35
39
  currentUserProperties as loadCurrentUserProperties,
36
40
  getSiteUser as fetchSiteUser,
37
41
  typeAhead,
38
42
  } from './internal/userRequests';
39
- import { fetchDigest } from './internal/digest';
43
+
44
+ type ListKey<T extends { LISTS: Record<string, unknown> }> = Extract<
45
+ keyof T['LISTS'],
46
+ string
47
+ >;
48
+
49
+ /**
50
+ * @deprecated SP.Utilities.Utility.SendEmail was retired by Microsoft on 2025-10-31
51
+ * and no longer sends emails. Migrate to Microsoft Graph API:
52
+ * POST /v1.0/me/sendMail (delegated) or POST /v1.0/users/{id}/sendMail (app)
53
+ * https://learn.microsoft.com/en-us/graph/api/user-sendmail
54
+ */
55
+ type EmailApi = ((
56
+ from: string,
57
+ to: string[] | string,
58
+ subject: string | undefined,
59
+ body: string
60
+ ) => ReturnType<typeof sendEmailRequest>) & {
61
+ send: (props: EmailProps) => ReturnType<typeof sendEmailRequest>;
62
+ };
40
63
 
41
64
  class HTTPSharePointRequests<
42
65
  T extends {
@@ -44,52 +67,79 @@ class HTTPSharePointRequests<
44
67
  FOLDERS?: Record<string, unknown>;
45
68
  }
46
69
  > {
47
- private baseURL: string;
48
- private endpoint: string;
49
- private listName: string;
50
- private instance: AxiosInstance;
51
- private sharepointVersion: SHAREPOINT_VER;
52
- private listItemEntityTypeFullName?: string;
70
+ private readonly baseURL: string;
71
+ private readonly instance: AxiosInstance;
72
+ private readonly listItemEntityTypeCache: Map<string, string>;
73
+ /**
74
+ * @deprecated SP.Utilities.Utility.SendEmail was retired by Microsoft on 2025-10-31.
75
+ * Migrate to Microsoft Graph API POST /v1.0/me/sendMail
76
+ * https://learn.microsoft.com/en-us/graph/api/user-sendmail
77
+ */
78
+ public readonly email: EmailApi;
53
79
 
54
80
  constructor(baseURL: string, config?: AxiosConfig) {
55
81
  this.baseURL = baseURL.endsWith('/') ? baseURL : `${baseURL}/`;
56
- this.endpoint = '/api';
57
- this.listName = '';
58
82
  this.instance = instance(this.baseURL, config);
59
- this.sharepointVersion = '2013';
60
- this.listItemEntityTypeFullName = undefined;
83
+ this.listItemEntityTypeCache = new Map<string, string>();
84
+ const buildEmailContext = () =>
85
+ this.createContext<'2013'>({
86
+ endpoint: '_api/SP.Utilities.Utility.SendEmail',
87
+ listName: '',
88
+ sharepointVersion: '2013',
89
+ });
90
+ this.email = Object.assign(
91
+ (
92
+ from: string,
93
+ to: string[] | string,
94
+ subject: string | undefined,
95
+ body: string
96
+ ) =>
97
+ sendEmailRequest(buildEmailContext(), {
98
+ From: from,
99
+ To: to,
100
+ Subject: subject,
101
+ Body: body,
102
+ }),
103
+ {
104
+ send: (props: EmailProps) => sendEmailRequest(buildEmailContext(), props),
105
+ }
106
+ );
61
107
  }
62
108
 
63
- private getContext<V extends SHAREPOINT_VER = '2013'>(): RequestContext<V> {
109
+ private createContext<V extends SHAREPOINT_VER = '2013'>({
110
+ endpoint,
111
+ listName,
112
+ sharepointVersion,
113
+ listItemEntityTypeFullName,
114
+ resolveListItemEntityType,
115
+ }: {
116
+ endpoint: string;
117
+ listName: string;
118
+ sharepointVersion: V;
119
+ listItemEntityTypeFullName?: string;
120
+ resolveListItemEntityType?: () => Promise<string>;
121
+ }): RequestContext<V> {
64
122
  return {
65
123
  instance: this.instance,
66
- baseURL: this.baseURL,
67
- endpoint: this.endpoint,
68
- listName: this.listName,
69
- sharepointVersion: this.sharepointVersion as V,
70
- listItemEntityTypeFullName: this.listItemEntityTypeFullName,
71
- resolveListItemEntityType: this.resolveListItemEntityTypeFullName.bind(
72
- this
73
- ),
124
+ endpoint,
125
+ listName,
126
+ sharepointVersion,
127
+ listItemEntityTypeFullName,
128
+ resolveListItemEntityType,
74
129
  };
75
130
  }
76
131
 
77
- private async resolveListItemEntityTypeFullName(): Promise<string> {
78
- if (this.listItemEntityTypeFullName) {
79
- return this.listItemEntityTypeFullName;
80
- }
81
-
82
- if (!this.listName) {
83
- throw new Error('List name is not set.');
84
- }
85
-
86
- if (this.sharepointVersion !== '2013') {
87
- this.listItemEntityTypeFullName = `SP.Data.${this.listName}ListItem`;
88
- return this.listItemEntityTypeFullName;
132
+ private async resolveListItemEntityTypeFullName(
133
+ listName: string
134
+ ): Promise<string> {
135
+ const cacheKey = listName;
136
+ const cachedType = this.listItemEntityTypeCache.get(cacheKey);
137
+ if (cachedType) {
138
+ return cachedType;
89
139
  }
90
140
 
91
141
  const response = await this.instance.get(
92
- `_api/web/lists/GetByTitle('${this.listName}')?$select=ListItemEntityTypeFullName`
142
+ `${buildListByTitleEndpoint(listName)}?$select=ListItemEntityTypeFullName`
93
143
  );
94
144
 
95
145
  const entityType =
@@ -98,161 +148,246 @@ class HTTPSharePointRequests<
98
148
 
99
149
  if (!entityType || typeof entityType !== 'string') {
100
150
  throw new Error(
101
- `Unable to resolve ListItemEntityTypeFullName for list "${this.listName}".`
151
+ `Unable to resolve ListItemEntityTypeFullName for list "${listName}".`
102
152
  );
103
153
  }
104
154
 
105
- this.listItemEntityTypeFullName = entityType;
106
- return this.listItemEntityTypeFullName;
155
+ this.listItemEntityTypeCache.set(cacheKey, entityType);
156
+ return entityType;
107
157
  }
108
158
 
109
- /**
110
- * @deprecated Use `from` instead.
111
- */
112
- list(listName: IListName<T>) {
113
- this.listName = listName;
114
- this.endpoint = `_vti_bin/ListData.svc/${listName}`;
115
- this.sharepointVersion = '2010';
116
- this.listItemEntityTypeFullName = undefined;
117
- return this;
118
- }
159
+ private buildListScope<
160
+ K extends ListKey<T>,
161
+ V extends SHAREPOINT_VER = '2013'
162
+ >(listName: K, version: V) {
163
+ const listNameValue = String(listName);
164
+ const endpoint =
165
+ version === '2013'
166
+ ? `${buildListByTitleEndpoint(listNameValue)}/items`
167
+ : `_vti_bin/ListData.svc/${encodeURIComponent(listNameValue)}`;
119
168
 
120
- public readonly lists = {
121
- get: () => {
122
- this.endpoint = '_api/web/lists';
123
- this.sharepointVersion = '2013';
124
- return getListItems(this.getContext());
125
- },
126
- };
169
+ let scopedEntityType: string | undefined;
170
+ const resolveListItemEntityType = async () => {
171
+ if (scopedEntityType) {
172
+ return scopedEntityType;
173
+ }
127
174
 
128
- public getDigest() {
129
- return fetchDigest(this.instance, this.baseURL);
130
- }
175
+ if (version !== '2013') {
176
+ scopedEntityType = `SP.Data.${listNameValue}ListItem`;
177
+ return scopedEntityType;
178
+ }
131
179
 
132
- public from<
133
- K extends Extract<keyof T['LISTS'], string>,
134
- V extends SHAREPOINT_VER = '2013'
135
- >(listName: K, sharepoint_ver?: V) {
136
- const version = (sharepoint_ver ?? '2013') as V;
180
+ scopedEntityType = await this.resolveListItemEntityTypeFullName(
181
+ listNameValue
182
+ );
183
+ return scopedEntityType;
184
+ };
137
185
 
138
- this.listName = listName;
139
- this.sharepointVersion = version;
140
- this.listItemEntityTypeFullName = undefined;
141
- this.endpoint =
142
- version === '2013'
143
- ? `_api/web/lists/GetByTitle('${this.listName}')/items`
144
- : `_vti_bin/ListData.svc/${listName}`;
186
+ const getContext = () =>
187
+ this.createContext<V>({
188
+ endpoint,
189
+ listName: listNameValue,
190
+ sharepointVersion: version,
191
+ listItemEntityTypeFullName: scopedEntityType,
192
+ resolveListItemEntityType,
193
+ });
145
194
 
146
- const getContext = () => this.getContext<V>();
195
+ const removeItem = (id: ItemID) => deleteItem(getContext(), id);
196
+ const removeFile = (props: DeleteFileProps) =>
197
+ deleteLibraryFile(getContext(), props);
147
198
 
148
199
  return {
149
200
  create: (data: ListData<T['LISTS'][K]>) =>
150
201
  createListItem<T['LISTS'][K], V>(getContext(), data),
151
202
  get: (options?: Options<T['LISTS'][K], V>) =>
152
203
  getListItems<T['LISTS'][K], V>(getContext(), options),
153
- update: (
154
- id: ItemID,
155
- data: ListData<T['LISTS'][K]>,
156
- digest?: string
157
- ) => updateListItem<T['LISTS'][K], V>(getContext(), id, data, digest),
158
- delete: (id: ItemID) => deleteItem(getContext(), id),
204
+ update: (id: ItemID, data: ListData<T['LISTS'][K]>, digest?: string) =>
205
+ updateListItem<T['LISTS'][K], V>(getContext(), id, data, digest),
206
+ delete: removeItem,
207
+ remove: removeItem,
159
208
  createFile: (props: CreateFileProps) =>
160
209
  createAttachment(getContext(), props),
161
210
  uploadFile: (props: UploadFileProps) => uploadFile(getContext(), props),
162
- deleteFile: (props: DeleteFileProps) =>
163
- deleteLibraryFile(getContext(), props),
211
+ deleteFile: removeFile,
212
+ removeFile,
164
213
  fields: (options?: Field_Options<T['LISTS'][K], V>) => {
165
- this.endpoint = !options?.fieldName
166
- ? `_api/web/lists/GetByTitle('${this.listName}')/fields`
167
- : `_api/web/lists/GetByTitle('${this.listName}')/fields/getbytitle('${options.fieldName}')`;
214
+ const fieldsEndpoint = !options?.fieldName
215
+ ? `${buildListByTitleEndpoint(listNameValue)}/fields`
216
+ : `${buildListByTitleEndpoint(
217
+ listNameValue
218
+ )}/fields/getbytitle('${escapeODataStringLiteral(
219
+ String(options.fieldName)
220
+ )}')`;
168
221
  return {
169
222
  get: () =>
170
- getOnly<T['LISTS'][K], V>(this.instance, this.endpoint, options),
223
+ getOnly<T['LISTS'][K], V>(this.instance, fieldsEndpoint, options),
171
224
  };
172
225
  },
226
+ items: {
227
+ create: (data: ListData<T['LISTS'][K]>) =>
228
+ createListItem<T['LISTS'][K], V>(getContext(), data),
229
+ get: (options?: Options<T['LISTS'][K], V>) =>
230
+ getListItems<T['LISTS'][K], V>(getContext(), options),
231
+ update: (id: ItemID, data: ListData<T['LISTS'][K]>, digest?: string) =>
232
+ updateListItem<T['LISTS'][K], V>(getContext(), id, data, digest),
233
+ delete: removeItem,
234
+ remove: removeItem,
235
+ },
236
+ files: {
237
+ attach: (props: CreateFileProps) =>
238
+ createAttachment(getContext(), props),
239
+ upload: (props: UploadFileProps) => uploadFile(getContext(), props),
240
+ delete: removeFile,
241
+ remove: removeFile,
242
+ },
173
243
  };
174
244
  }
175
245
 
176
- public folders = {
177
- get: () => {
178
- this.endpoint = '_api/web/folders';
179
- this.sharepointVersion = '2013';
180
- getListItems(this.getContext());
181
- },
182
- };
246
+ /**
247
+ * @deprecated Use `from(listName, '2010')` instead.
248
+ */
249
+ list<K extends ListKey<T>>(listName: K) {
250
+ return this.from<K, '2010'>(listName, '2010');
251
+ }
183
252
 
184
- public folder(folderName?: string | string[]) {
185
- let filePath = ['Shared Documents'];
253
+ public from<K extends ListKey<T>, V extends SHAREPOINT_VER = '2013'>(
254
+ listName: K,
255
+ sharepointVersion?: V
256
+ ) {
257
+ const version = (sharepointVersion ?? '2013') as V;
258
+ return this.buildListScope<K, V>(listName, version);
259
+ }
186
260
 
187
- if (folderName) {
188
- filePath = Array.isArray(folderName)
189
- ? [...filePath, ...folderName]
190
- : [...filePath, folderName];
191
- }
261
+ public field<K extends ListKey<T>, V extends SHAREPOINT_VER = '2013'>(
262
+ listName: K,
263
+ fieldName?: Extract<keyof T['LISTS'][K], string>,
264
+ sharepointVersion?: V
265
+ ) {
266
+ return this.from<K, V>(listName, sharepointVersion)
267
+ .fields({
268
+ fieldName,
269
+ } as Field_Options<T['LISTS'][K], V>)
270
+ .get();
271
+ }
272
+
273
+ public lists(options?: Options<Record<string, unknown>, '2013'>) {
274
+ const context = this.createContext<'2013'>({
275
+ endpoint: '_api/web/lists',
276
+ listName: '',
277
+ sharepointVersion: '2013',
278
+ });
279
+
280
+ return {
281
+ get: () => getListItems<Record<string, unknown>, '2013'>(context, options),
282
+ };
283
+ }
192
284
 
193
- this.endpoint = `_api/web/GetFolderByServerRelativeUrl('${filePath.join(
194
- '/'
195
- )}')`;
196
- this.sharepointVersion = '2013';
285
+ public getDigest() {
286
+ return fetchDigest(this.instance);
287
+ }
197
288
 
198
- const getContext = () => this.getContext();
289
+ public folders(options?: Folder_Options<Record<string, unknown>>) {
290
+ const context = this.createContext<'2013'>({
291
+ endpoint: '_api/web/folders',
292
+ listName: '',
293
+ sharepointVersion: '2013',
294
+ });
199
295
 
200
296
  return {
201
- get: (options?: Folder_Options<any>) => {
202
- if (!options?.type) {
203
- this.endpoint += '/Files';
204
- } else {
205
- this.endpoint += `/${options.type}`;
206
- }
207
- return getFiles(getContext(), options);
297
+ get: () => getFiles<Record<string, unknown>, '2013'>(context, options),
298
+ };
299
+ }
300
+
301
+ public folder(folderName?: string | string[]) {
302
+ const defaultPath = ['Shared Documents'];
303
+ const segments = folderName
304
+ ? Array.isArray(folderName)
305
+ ? [...defaultPath, ...folderName]
306
+ : [...defaultPath, ...folderName.split('/').filter(Boolean)]
307
+ : defaultPath;
308
+
309
+ const serverRelativePath = escapeODataStringLiteral(segments.join('/'));
310
+ const baseEndpoint = `_api/web/GetFolderByServerRelativeUrl('${serverRelativePath}')`;
311
+
312
+ return {
313
+ get: (options?: Folder_Options<Record<string, unknown>>) => {
314
+ const folderType = options?.type ?? 'Files';
315
+ const endpoint = `${baseEndpoint}/${folderType}`;
316
+ const context = this.createContext<'2013'>({
317
+ endpoint,
318
+ listName: '',
319
+ sharepointVersion: '2013',
320
+ });
321
+
322
+ return getFiles<Record<string, unknown>, '2013'>(context, {
323
+ ...options,
324
+ type: folderType,
325
+ });
208
326
  },
209
327
  };
210
328
  }
211
329
 
212
- public users(options?: Options<any>) {
213
- this.endpoint = '_api/web/SiteUserInfoList/items';
214
- this.sharepointVersion = '2013';
330
+ public users(options?: Options<Record<string, unknown>, '2013'>) {
331
+ const context = this.createContext<'2013'>({
332
+ endpoint: '_api/web/SiteUserInfoList/items',
333
+ listName: '',
334
+ sharepointVersion: '2013',
335
+ });
336
+
215
337
  return {
216
- get: () => getListItems(this.getContext(), options),
338
+ get: () => getListItems<Record<string, unknown>, '2013'>(context, options),
217
339
  };
218
340
  }
219
341
 
220
- public email = {
221
- send: (props: EmailProps) => {
222
- this.sharepointVersion = '2013';
223
- return sendEmailRequest(this.getContext(), props);
224
- },
225
- };
342
+ /**
343
+ * @deprecated SP.Utilities.Utility.SendEmail was retired by Microsoft on 2025-10-31.
344
+ * Migrate to Microsoft Graph API POST /v1.0/me/sendMail
345
+ * https://learn.microsoft.com/en-us/graph/api/user-sendmail
346
+ */
347
+ public sendEmail(props: EmailProps) {
348
+ const context = this.createContext<'2013'>({
349
+ endpoint: '_api/SP.Utilities.Utility.SendEmail',
350
+ listName: '',
351
+ sharepointVersion: '2013',
352
+ });
353
+ return sendEmailRequest(context, props);
354
+ }
226
355
 
227
- public user = {
356
+ public readonly user = {
228
357
  properties: () => ({
229
- get: (): Promise<UserProfile> => {
230
- this.sharepointVersion = '2013';
231
- return loadCurrentUserProperties(this.instance);
232
- },
358
+ get: (): Promise<UserProfile> => loadCurrentUserProperties(this.instance),
359
+ }),
360
+ searchSiteUser: (searchValue: string) =>
361
+ fetchSiteUser(this.instance, searchValue),
362
+ searchUser: (input: string, filter?: string): Promise<PersonField> =>
363
+ typeAhead(this.instance, input, filter),
364
+ current: () => ({
365
+ get: (): Promise<CurrentUser> =>
366
+ getOnly(this.instance, '_api/web/currentUser'),
233
367
  }),
234
- searchSiteUser: (searchValue: string) => {
235
- this.sharepointVersion = '2013';
236
- return fetchSiteUser(this.instance, searchValue);
237
- },
238
- searchUser: (input: string, filter?: string): Promise<PersonField> => {
239
- this.sharepointVersion = '2013';
240
- return typeAhead(this.instance, input, filter);
241
- },
242
- current: () => {
243
- this.endpoint = '_api/web/currentUser';
244
- this.sharepointVersion = '2013';
245
- return {
246
- get: (): Promise<CurrentUser> => getOnly(this.instance, this.endpoint),
247
- };
248
- },
249
368
  currentPermission: () => ({
250
- get: (): Promise<UserPermision> => {
251
- this.sharepointVersion = '2013';
252
- return resolveCurrentUserPermissions(this.getContext());
253
- },
369
+ get: (): Promise<UserPermision> =>
370
+ resolveCurrentUserPermissions(
371
+ this.createContext<'2013'>({
372
+ endpoint: '_api/web/effectiveBasePermissions',
373
+ listName: '',
374
+ sharepointVersion: '2013',
375
+ })
376
+ ),
254
377
  }),
255
378
  };
379
+
380
+ public getCurrentUser() {
381
+ return this.user.current().get();
382
+ }
383
+
384
+ public currentUserProperties() {
385
+ return this.user.properties().get();
386
+ }
387
+
388
+ public currentUserPermission() {
389
+ return this.user.currentPermission().get();
390
+ }
256
391
  }
257
392
 
258
393
  export default HTTPSharePointRequests;
@@ -7,6 +7,13 @@ const defaultConfig: AxiosConfig = {
7
7
  },
8
8
  timeout: 15000,
9
9
  withCredentials: true,
10
+ paramsSerializer: {
11
+ serialize: (params: Record<string, string | number | undefined>) =>
12
+ Object.entries(params)
13
+ .filter(([, v]) => v !== undefined && v !== null)
14
+ .map(([k, v]) => `${k}=${String(v)}`)
15
+ .join('&'),
16
+ },
10
17
  };
11
18
 
12
19
  export const instance = (baseURL: string, config?: AxiosConfig) => {
@@ -1,9 +1,8 @@
1
- import type { AxiosInstance } from 'axios';
2
- import type { SHAREPOINT_VER } from '../../types';
1
+ import { AxiosInstance } from 'axios';
2
+ import { SHAREPOINT_VER } from '../../types';
3
3
 
4
4
  export interface RequestContext<V extends SHAREPOINT_VER = '2013'> {
5
5
  instance: AxiosInstance;
6
- baseURL: string;
7
6
  endpoint: string;
8
7
  listName: string;
9
8
  sharepointVersion: V;
@@ -1,22 +1,29 @@
1
- import type { AxiosInstance } from 'axios';
1
+ import { AxiosInstance } from 'axios';
2
2
 
3
- export async function fetchDigest(
4
- instance: AxiosInstance,
5
- baseURL: string
6
- ): Promise<string> {
7
- try {
8
- const response = await instance.post(`${baseURL}_api/contextinfo`);
3
+ type DigestEntry = { value: string; expiresAt: number };
4
+ const cache = new WeakMap<AxiosInstance, DigestEntry>();
9
5
 
10
- const formDigestValue =
11
- response.data?.d?.GetContextWebInformation?.FormDigestValue;
6
+ export async function fetchDigest(instance: AxiosInstance): Promise<string> {
7
+ const cached = cache.get(instance);
8
+ if (cached && Date.now() < cached.expiresAt) {
9
+ return cached.value;
10
+ }
11
+
12
+ try {
13
+ const response = await instance.post('_api/contextinfo');
14
+ const info = response.data?.d?.GetContextWebInformation;
15
+ const formDigestValue = info?.FormDigestValue;
12
16
 
13
17
  if (!formDigestValue) {
14
18
  throw new Error('Invalid response structure: Missing FormDigestValue');
15
19
  }
16
20
 
21
+ const ttl = (info?.FormDigestTimeoutSeconds ?? 1800) - 60;
22
+ cache.set(instance, { value: formDigestValue, expiresAt: Date.now() + ttl * 1000 });
23
+
17
24
  return formDigestValue;
18
25
  } catch (error) {
19
- console.error('Failed to retrieve the Form Digest Value:', error);
20
- throw new Error(`Error retrieving Form Digest Value`);
26
+ const cause = error instanceof Error ? error.message : 'Unknown contextinfo error';
27
+ throw new Error(`Error retrieving Form Digest Value: ${cause}`);
21
28
  }
22
29
  }
@@ -1,7 +1,7 @@
1
- import type { AxiosResponse } from 'axios';
2
- import type { EmailProps } from '../../types';
1
+ import { AxiosResponse } from 'axios';
2
+ import { EmailProps } from '../../types';
3
3
  import { fetchDigest } from './digest';
4
- import type { RequestContext } from './context';
4
+ import { RequestContext } from './context';
5
5
 
6
6
  type SendEmailResponse = AxiosResponse<{
7
7
  d: {
@@ -13,17 +13,11 @@ export async function sendEmail(
13
13
  context: RequestContext,
14
14
  { From, To, Subject, Body }: EmailProps
15
15
  ): Promise<SendEmailResponse> {
16
- const httpRequest = '_api/SP.Utilities.Utility.SendEmail';
17
-
18
16
  const response = await context.instance({
19
- url: httpRequest,
17
+ url: '_api/SP.Utilities.Utility.SendEmail',
20
18
  method: 'POST',
21
19
  headers: {
22
- 'X-RequestDigest': await fetchDigest(
23
- context.instance,
24
- context.baseURL
25
- ),
26
- 'X-Http-Method': 'POST',
20
+ 'X-RequestDigest': await fetchDigest(context.instance),
27
21
  'Content-Type': 'application/json;odata=verbose',
28
22
  },
29
23
  data: JSON.stringify({
@@ -1,17 +1,28 @@
1
- import type { CreateFileProps, SHAREPOINT_VER } from '../../../types';
1
+ import { CreateFileProps, SHAREPOINT_VER } from '../../../types';
2
2
  import { fetchDigest } from '../digest';
3
- import type { RequestContext } from '../context';
3
+ import { RequestContext } from '../context';
4
+ import { buildListByTitleEndpoint } from '../sharepointUrl';
5
+ import { encodeFileName } from './utils';
4
6
 
5
7
  export async function createAttachment<V extends SHAREPOINT_VER = '2013'>(
6
8
  context: RequestContext<V>,
7
9
  { file, itemId }: CreateFileProps
8
10
  ) {
9
- const requestUrl = `_api/lists/getbytitle('${context.listName}')/items(${itemId})/AttachmentFiles/add(FileName='${file.name}')`;
11
+ if (context.sharepointVersion !== '2013') {
12
+ throw new Error(
13
+ 'createAttachment is only supported for SharePoint 2013 REST endpoints.'
14
+ );
15
+ }
16
+
17
+ const encodedFileName = encodeFileName(file.name);
18
+ const requestUrl = `${buildListByTitleEndpoint(
19
+ context.listName
20
+ )}/items(${itemId})/AttachmentFiles/add(FileName='${encodedFileName}')`;
21
+
10
22
  const response = await context.instance.post(requestUrl, file, {
11
23
  headers: {
12
- 'X-RequestDigest': await fetchDigest(context.instance, context.baseURL),
24
+ 'X-RequestDigest': await fetchDigest(context.instance),
13
25
  },
14
- withCredentials: true,
15
26
  });
16
27
 
17
28
  return response;