microsoft-graph 1.0.0

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 (151) hide show
  1. package/.editorconfig +18 -0
  2. package/.gitattributes +46 -0
  3. package/.vscode/extensions.json +3 -0
  4. package/.vscode/launch.json +15 -0
  5. package/.vscode/settings.json +15 -0
  6. package/CONTRIBUTING.md +2 -0
  7. package/LICENCE.md +360 -0
  8. package/README.md +52 -0
  9. package/biome.jsonc +83 -0
  10. package/dist/errors/BadTemplateError.js +6 -0
  11. package/dist/errors/EnvironmentVariableMissingError.js +6 -0
  12. package/dist/errors/InvalidArgumentError.js +21 -0
  13. package/dist/errors/ProtocolError.js +11 -0
  14. package/dist/errors/RequestFailedError.js +21 -0
  15. package/dist/graphApi.js +116 -0
  16. package/dist/index.js +2 -0
  17. package/dist/models/AccessToken.js +1 -0
  18. package/dist/models/DriveId.js +1 -0
  19. package/dist/models/DriveItemId.js +1 -0
  20. package/dist/models/DriveItemPath.js +1 -0
  21. package/dist/models/DriveItemRef.js +1 -0
  22. package/dist/models/DriveRef.js +1 -0
  23. package/dist/models/GraphOperation.js +1 -0
  24. package/dist/models/GraphOperationDefinition.js +1 -0
  25. package/dist/models/HostName.js +1 -0
  26. package/dist/models/Scope.js +1 -0
  27. package/dist/models/SiteId.js +1 -0
  28. package/dist/models/SiteName.js +1 -0
  29. package/dist/models/SiteRef.js +1 -0
  30. package/dist/models/WorkbookNamedRangeRef.js +1 -0
  31. package/dist/models/WorkbookRangeAddress.js +1 -0
  32. package/dist/models/WorkbookRangeName.js +1 -0
  33. package/dist/models/WorkbookRangeRef.js +1 -0
  34. package/dist/models/WorkbookRef.js +1 -0
  35. package/dist/models/WorkbookSessionId.js +1 -0
  36. package/dist/models/WorkbookTableId.js +1 -0
  37. package/dist/models/WorkbookTableRef.js +1 -0
  38. package/dist/models/WorkbookWorksheetId.js +1 -0
  39. package/dist/models/WorkbookWorksheetName.js +1 -0
  40. package/dist/models/WorkbookWorksheetRef.js +1 -0
  41. package/dist/operations/drive/createFolder.js +27 -0
  42. package/dist/operations/drive/createFolder.test.js +31 -0
  43. package/dist/operations/drive/listDrives.js +23 -0
  44. package/dist/operations/drive/listDrives.test.js +9 -0
  45. package/dist/operations/driveItem/copyDriveItem.js +21 -0
  46. package/dist/operations/driveItem/copyDriveItem.test.js +28 -0
  47. package/dist/operations/driveItem/deleteDriveItem.js +12 -0
  48. package/dist/operations/driveItem/deleteDriveItem.test.js +22 -0
  49. package/dist/operations/driveItem/getDriveItem.js +18 -0
  50. package/dist/operations/driveItem/getDriveItem.test.js +24 -0
  51. package/dist/operations/driveItem/getDriveItemByPath.js +20 -0
  52. package/dist/operations/driveItem/getDriveItemByPath.test.js +22 -0
  53. package/dist/operations/driveItem/getDriveItemContent.js +22 -0
  54. package/dist/operations/driveItem/getDriveItemContent.test.js +37 -0
  55. package/dist/operations/driveItem/listDriveItems.js +25 -0
  56. package/dist/operations/driveItem/listDriveItems.test.js +24 -0
  57. package/dist/operations/site/getSite.js +18 -0
  58. package/dist/operations/site/getSite.test.js +15 -0
  59. package/dist/operations/site/getSiteByName.js +20 -0
  60. package/dist/operations/site/getSiteByName.test.js +20 -0
  61. package/dist/operations/site/listSites.js +23 -0
  62. package/dist/operations/site/listSites.test.js +10 -0
  63. package/dist/operations/site/searchSites.js +23 -0
  64. package/dist/operations/site/searchSites.test.js +21 -0
  65. package/dist/operations/workbook/calculateWorkbook.js +20 -0
  66. package/dist/operations/workbook/calculateWorkbook.test.js +46 -0
  67. package/dist/operations/workbook/createWorkbook.js +26 -0
  68. package/dist/operations/workbook/createWorkbook.test.js +15 -0
  69. package/dist/operations/workbook/deleteWorkbook.js +5 -0
  70. package/dist/operations/workbookRange/clearWorkbookRange.js +17 -0
  71. package/dist/operations/workbookRange/clearWorkbookRange.test.js +56 -0
  72. package/dist/operations/workbookRange/deleteWorkbookRange.js +17 -0
  73. package/dist/operations/workbookRange/deleteWorkbookRange.test.js +56 -0
  74. package/dist/operations/workbookRange/getWorkbookNamedRange.js +20 -0
  75. package/dist/operations/workbookRange/getWorkbookUsedRange.js +22 -0
  76. package/dist/operations/workbookRange/getWorkbookUsedRange.test.js +54 -0
  77. package/dist/operations/workbookRange/getWorkbookVisibleRange.js +20 -0
  78. package/dist/operations/workbookRange/getWorkbookVisibleRange.test.js +109 -0
  79. package/dist/operations/workbookRange/insertWorkbookCells.js +25 -0
  80. package/dist/operations/workbookRange/insertWorkbookCells.test.js +41 -0
  81. package/dist/operations/workbookRange/updateWorkbookNamedRange.js +15 -0
  82. package/dist/operations/workbookRange/updateWorkbookRange.js +21 -0
  83. package/dist/operations/workbookRange/updateWorkbookRange.test.js +54 -0
  84. package/dist/operations/workbookSession/closeWorkbookSession.js +18 -0
  85. package/dist/operations/workbookSession/createWorkbookSession.js +22 -0
  86. package/dist/operations/workbookSession/refreshWorkbookSession.js +19 -0
  87. package/dist/operations/workbookTable/createWorkbookTable.js +26 -0
  88. package/dist/operations/workbookTable/createWorkbookTable.test.js +25 -0
  89. package/dist/operations/workbookTable/getWorkbookTable.js +20 -0
  90. package/dist/operations/workbookTable/getWorkbookTable.test.js +30 -0
  91. package/dist/operations/workbookTable/getWorkbookTableBodyRange.js +22 -0
  92. package/dist/operations/workbookTable/getWorkbookTableBodyRange.test.js +45 -0
  93. package/dist/operations/workbookTable/getWorkbookTableHeaderRange.js +22 -0
  94. package/dist/operations/workbookTable/getWorkbookTableHeaderRange.test.js +41 -0
  95. package/dist/operations/workbookTable/listWorkbookTableColumns.js +17 -0
  96. package/dist/operations/workbookTable/listWorkbookTableColumns.test.js +29 -0
  97. package/dist/operations/workbookTable/listWorkbookTableRows.js +17 -0
  98. package/dist/operations/workbookTable/listWorkbookTableRows.test.js +29 -0
  99. package/dist/operations/workbookTable/listWorkbookTables.js +25 -0
  100. package/dist/operations/workbookTable/listWorkbookTables.test.js +30 -0
  101. package/dist/operations/workbookWorksheet/createWorkbookWorksheet.js +25 -0
  102. package/dist/operations/workbookWorksheet/createWorkbookWorksheet.test.js +36 -0
  103. package/dist/operations/workbookWorksheet/deleteWorkbookWorksheet.js +14 -0
  104. package/dist/operations/workbookWorksheet/deleteWorkbookWorksheet.test.js +28 -0
  105. package/dist/operations/workbookWorksheet/getWorkbookWorksheetRange.js +20 -0
  106. package/dist/operations/workbookWorksheet/getWorkbookWorksheetRange.test.js +30 -0
  107. package/dist/operations/workbookWorksheet/listWorkbookWorksheets.js +28 -0
  108. package/dist/operations/workbookWorksheet/listWorkbookWorksheets.test.js +45 -0
  109. package/dist/operations/workbookWorksheet/updateWorkbookWorksheet.js +21 -0
  110. package/dist/operations/workbookWorksheet/updateWorkbookWorksheet.test.js +37 -0
  111. package/dist/services/accessToken.js +15 -0
  112. package/dist/services/address.js +3 -0
  113. package/dist/services/configuration.js +20 -0
  114. package/dist/services/drive.js +17 -0
  115. package/dist/services/driveItem.js +48 -0
  116. package/dist/services/driveItem.test.js +17 -0
  117. package/dist/services/httpAgent.js +9 -0
  118. package/dist/services/httpStatus.js +3 -0
  119. package/dist/services/operationId.js +6 -0
  120. package/dist/services/sharepointUrl.js +24 -0
  121. package/dist/services/sharepointUrl.test.js +32 -0
  122. package/dist/services/site.js +15 -0
  123. package/dist/services/sleep.js +3 -0
  124. package/dist/services/stringCaseConversion.js +5 -0
  125. package/dist/services/stringCaseConversion.test.js +16 -0
  126. package/dist/services/templatedPaths.js +20 -0
  127. package/dist/services/templatedPaths.test.js +49 -0
  128. package/dist/services/temporaryFiles.js +11 -0
  129. package/dist/services/workbookRange.js +10 -0
  130. package/dist/services/workbookRangeAddress.js +8 -0
  131. package/dist/services/workbookTable.js +13 -0
  132. package/dist/services/workbookWorksheet.js +13 -0
  133. package/dist/tasks/createWorkbookAndStartSession.js +10 -0
  134. package/dist/tasks/deleteDriveItemWithRetry.js +16 -0
  135. package/dist/tasks/downloadDriveItemContent.js +7 -0
  136. package/dist/tasks/endSessionAndDeleteWorkbook.js +6 -0
  137. package/dist/tasks/getRangeLastUsedCell.js +38 -0
  138. package/dist/tasks/getRangeLastUsedCell.test.js +89 -0
  139. package/dist/tasks/getWorkbookTableVisibleBody.js +24 -0
  140. package/dist/tasks/getWorkbookTableVisibleBody.test.js +104 -0
  141. package/dist/tasks/getWorkbookWorksheetRefByName.js +10 -0
  142. package/dist/tasks/setColumnHidden.js +7 -0
  143. package/dist/tasks/setColumnHidden.test.js +43 -0
  144. package/dist/tasks/setRowHidden.js +7 -0
  145. package/dist/tasks/setRowHidden.test.js +42 -0
  146. package/docs/approach.md +7 -0
  147. package/docs/calculateWorkbook.md +20 -0
  148. package/docs/concepts.md +4 -0
  149. package/docs/envs.md +11 -0
  150. package/docs/performance.md +24 -0
  151. package/package.json +426 -0
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getDefaultDriveRef } from "../../services/drive.js";
3
+ import { driveItemPath, } from "../../services/driveItem.js";
4
+ import { generateTempFileName } from "../../services/temporaryFiles.js";
5
+ import deleteDriveItemWithRetry from "../../tasks/deleteDriveItemWithRetry.js";
6
+ import createWorkbook from "../workbook/createWorkbook.js";
7
+ import createWorkbookWorksheet from "./createWorkbookWorksheet.js";
8
+ describe("createWorkbookWorksheet", () => {
9
+ it("can create a new worksheet in an existing workbook", { timeout: 10000 }, async () => {
10
+ const workbookName = generateTempFileName("xlsx");
11
+ const workbookPath = driveItemPath(workbookName);
12
+ const driveRef = getDefaultDriveRef();
13
+ const workbook = await createWorkbook(driveRef, workbookPath);
14
+ const worksheetName = "Sheet2";
15
+ try {
16
+ const worksheet = await createWorkbookWorksheet(workbook, worksheetName);
17
+ expect(worksheet.name).toBe(worksheetName);
18
+ }
19
+ finally {
20
+ await deleteDriveItemWithRetry(workbook);
21
+ }
22
+ });
23
+ it("can create a new worksheet without a name", { timeout: 10000 }, async () => {
24
+ const workbookName = generateTempFileName("xlsx");
25
+ const workbookPath = driveItemPath(workbookName);
26
+ const driveRef = getDefaultDriveRef();
27
+ const workbook = await createWorkbook(driveRef, workbookPath);
28
+ try {
29
+ const worksheet = await createWorkbookWorksheet(workbook);
30
+ expect(worksheet.name).toBeDefined();
31
+ }
32
+ finally {
33
+ await deleteDriveItemWithRetry(workbook);
34
+ }
35
+ });
36
+ });
@@ -0,0 +1,14 @@
1
+ import { operation } from "../../graphApi.js";
2
+ import { generatePath } from "../../services/templatedPaths.js";
3
+ /** Permanently delete a worksheet. @see https://learn.microsoft.com/en-us/graph/api/worksheet-delete */
4
+ export default function deleteWorkbookWorksheet(worksheetRef) {
5
+ return operation({
6
+ method: "DELETE",
7
+ path: generatePath("/sites/{site-id}/drives/{drive-id}/items/{item-id}/workbook/worksheets/{worksheet-id}", worksheetRef),
8
+ headers: {
9
+ "workbook-session-id": worksheetRef.sessionId,
10
+ },
11
+ body: null,
12
+ responseTransform: () => undefined
13
+ });
14
+ }
@@ -0,0 +1,28 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getDefaultDriveRef } from "../../services/drive.js";
3
+ import { driveItemPath, } from "../../services/driveItem.js";
4
+ import { generateTempFileName } from "../../services/temporaryFiles.js";
5
+ import deleteDriveItemWithRetry from "../../tasks/deleteDriveItemWithRetry.js";
6
+ import calculateWorkbook from "../workbook/calculateWorkbook.js";
7
+ import createWorkbook from "../workbook/createWorkbook.js";
8
+ import createWorkbookWorksheet from "./createWorkbookWorksheet.js";
9
+ import deleteWorkbookWorksheet from "./deleteWorkbookWorksheet.js";
10
+ import listWorkbookWorksheets from "./listWorkbookWorksheets.js";
11
+ describe("deleteWorkbookWorksheet", () => {
12
+ it("can delete a worksheet from an existing workbook", { timeout: 10000 }, async () => {
13
+ const workbookName = generateTempFileName("xlsx");
14
+ const workbookPath = driveItemPath(workbookName);
15
+ const driveRef = getDefaultDriveRef();
16
+ const workbook = await createWorkbook(driveRef, workbookPath);
17
+ try {
18
+ const worksheet = await createWorkbookWorksheet(workbook);
19
+ await deleteWorkbookWorksheet(worksheet);
20
+ await calculateWorkbook(workbook);
21
+ const worksheets = await listWorkbookWorksheets(workbook);
22
+ expect(worksheets.some(ws => ws.id === worksheet.worksheetId)).toBe(false);
23
+ }
24
+ finally {
25
+ await deleteDriveItemWithRetry(workbook);
26
+ }
27
+ });
28
+ });
@@ -0,0 +1,20 @@
1
+ import { operation } from "../../graphApi.js";
2
+ import { generatePath } from "../../services/templatedPaths.js";
3
+ /** Fetch a range, including values and formatting. @see https://learn.microsoft.com/en-us/graph/api/range-get */
4
+ export default function getWorkbookWorksheetRange(rangeRef) {
5
+ return operation({
6
+ method: "GET",
7
+ path: generatePath(`/sites/{site-id}/drives/{drive-id}/items/{item-id}/workbook/worksheets/{worksheet-id}/range(address='${rangeRef.address}')`, rangeRef),
8
+ headers: {
9
+ "workbook-session-id": rangeRef.sessionId,
10
+ },
11
+ body: null,
12
+ responseTransform: response => {
13
+ const range = response;
14
+ return {
15
+ ...range,
16
+ ...rangeRef
17
+ };
18
+ }
19
+ });
20
+ }
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getDefaultDriveRef } from "../../services/drive.js";
3
+ import { driveItemPath, } from "../../services/driveItem.js";
4
+ import { generateTempFileName } from "../../services/temporaryFiles.js";
5
+ import { workbookRangeRef } from "../../services/workbookRange.js";
6
+ import { defaultWorkbookWorksheetId, workbookWorksheetRef } from "../../services/workbookWorksheet.js";
7
+ import deleteDriveItemWithRetry from "../../tasks/deleteDriveItemWithRetry.js";
8
+ import createWorkbook from "../workbook/createWorkbook.js";
9
+ import updateWorkbookRange from "../workbookRange/updateWorkbookRange.js";
10
+ describe("getWorkbookWorksheetRange", () => {
11
+ it("can retrieve a range from an existing worksheet", { timeout: 10000 }, async () => {
12
+ const address = "A1:B2";
13
+ const values = [[1, 2], [3, 4]];
14
+ const workbookName = generateTempFileName("xlsx");
15
+ const workbookPath = driveItemPath(workbookName);
16
+ const driveRef = getDefaultDriveRef();
17
+ const workbook = await createWorkbook(driveRef, workbookPath);
18
+ const worksheetRef = workbookWorksheetRef(workbook, defaultWorkbookWorksheetId);
19
+ const rangeRef = workbookRangeRef(worksheetRef, address);
20
+ try {
21
+ const range = await updateWorkbookRange(rangeRef, {
22
+ values: values
23
+ });
24
+ expect(range.values).toEqual(values);
25
+ }
26
+ finally {
27
+ await deleteDriveItemWithRetry(workbook);
28
+ }
29
+ });
30
+ });
@@ -0,0 +1,28 @@
1
+ import ProtocolError from "../../errors/ProtocolError.js";
2
+ import { operation } from "../../graphApi.js";
3
+ import { generatePath } from "../../services/templatedPaths.js";
4
+ import { workbookWorksheetRef } from "../../services/workbookWorksheet.js";
5
+ /** Retrieve a list of worksheets. @see https://learn.microsoft.com/en-us/graph/api/worksheet-list */
6
+ export default function listWorkbookWorksheets(workbookRef) {
7
+ return operation({
8
+ method: "GET",
9
+ path: generatePath("/sites/{site-id}/drives/{drive-id}/items/{item-id}/workbook/worksheets", workbookRef),
10
+ headers: {
11
+ "workbook-session-id": workbookRef.sessionId,
12
+ },
13
+ body: null,
14
+ responseTransform: response => {
15
+ const worksheets = response;
16
+ return worksheets.value.map(worksheet => {
17
+ const worksheetRef = workbookWorksheetRef(workbookRef, worksheet.id);
18
+ if (!worksheet.name) {
19
+ throw new ProtocolError("Item.name is undefined");
20
+ }
21
+ return {
22
+ ...worksheet,
23
+ ...worksheetRef,
24
+ };
25
+ });
26
+ }
27
+ });
28
+ }
@@ -0,0 +1,45 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { sequential } from "../../graphApi.js";
3
+ import { getDefaultDriveRef } from "../../services/drive.js";
4
+ import { driveItemPath, } from "../../services/driveItem.js";
5
+ import { generateTempFileName } from "../../services/temporaryFiles.js";
6
+ import deleteDriveItemWithRetry from "../../tasks/deleteDriveItemWithRetry.js";
7
+ import calculateWorkbook from "../workbook/calculateWorkbook.js";
8
+ import createWorkbook from "../workbook/createWorkbook.js";
9
+ import createWorkbookWorksheet from "./createWorkbookWorksheet.js";
10
+ import listWorkbookWorksheets from "./listWorkbookWorksheets.js";
11
+ describe("listWorkbookWorksheets", () => {
12
+ it("can list worksheets in an existing workbook", { timeout: 10000 }, async () => {
13
+ const workbookName = generateTempFileName("xlsx");
14
+ const workbookPath = driveItemPath(workbookName);
15
+ const driveRef = getDefaultDriveRef();
16
+ const workbook = await createWorkbook(driveRef, workbookPath);
17
+ try {
18
+ const worksheet1 = await createWorkbookWorksheet(workbook);
19
+ const worksheet2 = await createWorkbookWorksheet(workbook);
20
+ await calculateWorkbook(workbook);
21
+ const worksheets = await listWorkbookWorksheets(workbook);
22
+ const worksheetIds = worksheets.map(ws => ws.worksheetId);
23
+ expect(worksheetIds).toContain(worksheet1.worksheetId);
24
+ expect(worksheetIds).toContain(worksheet2.worksheetId);
25
+ }
26
+ finally {
27
+ await deleteDriveItemWithRetry(workbook);
28
+ }
29
+ });
30
+ it("can list worksheets in an existing workbook parallel", { timeout: 10000 }, async () => {
31
+ const workbookName = generateTempFileName("xlsx");
32
+ const workbookPath = driveItemPath(workbookName);
33
+ const driveRef = getDefaultDriveRef();
34
+ const workbook = await createWorkbook(driveRef, workbookPath);
35
+ try {
36
+ const [worksheet1, worksheet2, _, worksheets] = await sequential(createWorkbookWorksheet(workbook), createWorkbookWorksheet(workbook), calculateWorkbook(workbook), listWorkbookWorksheets(workbook));
37
+ const worksheetIds = worksheets.map(worksheet => worksheet.worksheetId);
38
+ expect(worksheetIds).toContain(worksheet1.worksheetId);
39
+ expect(worksheetIds).toContain(worksheet2.worksheetId);
40
+ }
41
+ finally {
42
+ await deleteDriveItemWithRetry(workbook);
43
+ }
44
+ });
45
+ });
@@ -0,0 +1,21 @@
1
+ import { operation } from "../../graphApi.js";
2
+ import { generatePath } from "../../services/templatedPaths.js";
3
+ /** Update the name, position and/or visibility of a worksheet. @see https://learn.microsoft.com/en-us/graph/api/worksheet-update */
4
+ export default function updateWorkbookWorksheet(worksheetRef, updates) {
5
+ return operation({
6
+ method: "PATCH",
7
+ path: generatePath("/sites/{site-id}/drives/{drive-id}/items/{item-id}/workbook/worksheets/{worksheet-id}", worksheetRef),
8
+ headers: {
9
+ "workbook-session-id": worksheetRef.sessionId,
10
+ "content-type": "application/json",
11
+ },
12
+ body: updates,
13
+ responseTransform: response => {
14
+ const worksheet = response;
15
+ return {
16
+ ...worksheet,
17
+ ...worksheetRef
18
+ };
19
+ }
20
+ });
21
+ }
@@ -0,0 +1,37 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getDefaultDriveRef } from "../../services/drive.js";
3
+ import { driveItemPath, } from "../../services/driveItem.js";
4
+ import { generateTempFileName } from "../../services/temporaryFiles.js";
5
+ import deleteDriveItemWithRetry from "../../tasks/deleteDriveItemWithRetry.js";
6
+ import createWorkbook from "../workbook/createWorkbook.js";
7
+ import createWorkbookWorksheet from "./createWorkbookWorksheet.js";
8
+ import updateWorkbookWorksheet from "./updateWorkbookWorksheet.js";
9
+ describe("updateWorkbookWorksheet", () => {
10
+ it("can update the name of an existing worksheet", { timeout: 10000 }, async () => {
11
+ const workbookName = generateTempFileName("xlsx");
12
+ const workbookPath = driveItemPath(workbookName);
13
+ const workbook = await createWorkbook(getDefaultDriveRef(), workbookPath);
14
+ try {
15
+ const worksheet = await createWorkbookWorksheet(workbook);
16
+ const newName = "UpdatedSheet";
17
+ const updatedWorksheet = await updateWorkbookWorksheet(worksheet, { name: newName });
18
+ expect(updatedWorksheet.name).toBe(newName);
19
+ }
20
+ finally {
21
+ await deleteDriveItemWithRetry(workbook);
22
+ }
23
+ });
24
+ it("can update the visibility of an existing worksheet", { timeout: 10000 }, async () => {
25
+ const workbookName = generateTempFileName("xlsx");
26
+ const workbookPath = driveItemPath(workbookName);
27
+ const workbook = await createWorkbook(getDefaultDriveRef(), workbookPath);
28
+ try {
29
+ const worksheet = await createWorkbookWorksheet(workbook);
30
+ const updatedWorksheet = await updateWorkbookWorksheet(worksheet, { visibility: "Hidden" });
31
+ expect(updatedWorksheet.visibility).toBe("Hidden");
32
+ }
33
+ finally {
34
+ await deleteDriveItemWithRetry(workbook);
35
+ }
36
+ });
37
+ });
@@ -0,0 +1,15 @@
1
+ import { ClientSecretCredential } from "@azure/identity";
2
+ import { azureClientIdEnv, azureClientSecretEnv, azureTenantIdEnv } from "./configuration.js";
3
+ const innerTokenCache = {};
4
+ /** Get an access token for a given scope. If an unexpired one is cached it will be returned, otherwise requests a new one. */
5
+ export async function getCurrentAccessToken(scope) {
6
+ let innerToken = innerTokenCache[scope];
7
+ if (!innerToken || innerToken.expiresOnTimestamp < Date.now()) {
8
+ const azureTenantId = azureTenantIdEnv();
9
+ const azureClientId = azureClientIdEnv();
10
+ const azureClientSecret = azureClientSecretEnv();
11
+ const credential = new ClientSecretCredential(azureTenantId, azureClientId, azureClientSecret);
12
+ innerTokenCache[scope] = innerToken = await credential.getToken(scope);
13
+ }
14
+ return innerToken.token;
15
+ }
@@ -0,0 +1,3 @@
1
+ export function indexesToAddress(row, col) {
2
+ return `${String.fromCharCode(65 + col)}${row + 1}`;
3
+ }
@@ -0,0 +1,20 @@
1
+ import EnvironmentVariableMissingError from "../errors/EnvironmentVariableMissingError.js";
2
+ function get(env, fallbackValue = null) {
3
+ const value = process.env[env]?.trim() ?? "";
4
+ if (value === "") {
5
+ if (fallbackValue === null) {
6
+ throw new EnvironmentVariableMissingError(env);
7
+ }
8
+ return fallbackValue ?? "";
9
+ }
10
+ return value;
11
+ }
12
+ function bind(env, fallbackValue = null) {
13
+ return () => get(env, fallbackValue);
14
+ }
15
+ export const azureTenantIdEnv = bind("AZURE_TENANT_ID");
16
+ export const azureClientIdEnv = bind("AZURE_CLIENT_ID");
17
+ export const azureClientSecretEnv = bind("AZURE_CLIENT_SECRET");
18
+ export const defaultSiteIdEnv = bind("SHAREPOINT_DEFAULT_SITE_ID");
19
+ export const defaultDriveIdEnv = bind("SHAREPOINT_DEFAULT_DRIVE_ID");
20
+ export const httpProxyEnv = bind("HTTP_PROXY", "");
@@ -0,0 +1,17 @@
1
+ import ProtocolError from "../errors/ProtocolError.js";
2
+ import { defaultDriveIdEnv, defaultSiteIdEnv } from "./configuration.js";
3
+ export function driveRef(siteRef, driveId) {
4
+ if (!driveId) {
5
+ throw new ProtocolError("DriveID is missing");
6
+ }
7
+ return {
8
+ siteId: siteRef.siteId,
9
+ driveId,
10
+ };
11
+ }
12
+ export function getDefaultDriveRef() {
13
+ return {
14
+ siteId: defaultSiteIdEnv(),
15
+ driveId: defaultDriveIdEnv()
16
+ };
17
+ }
@@ -0,0 +1,48 @@
1
+ import InvalidArgumentError from "../errors/InvalidArgumentError.js";
2
+ import ProtocolError from "../errors/ProtocolError.js";
3
+ const segmentPattern = /^[^"*:<>?\\|#]{1,256}$/;
4
+ const reservedNames = [
5
+ "CON", "PRN", "AUX", "NUL",
6
+ "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
7
+ "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
8
+ ];
9
+ export const workbookFileExtension = "xlsx";
10
+ export const rootDriveItemPath = driveItemPath("/");
11
+ /** Create a drive item path from a given set of segments. Ie ["a","b"] => "/a/b" */
12
+ export function driveItemPath(...segments) {
13
+ for (const segment of segments) {
14
+ if (segment === "") {
15
+ throw new InvalidArgumentError("Segment cannot be an empty string.");
16
+ }
17
+ if (!segmentPattern.test(segment)) {
18
+ throw new InvalidArgumentError(`Segment '${segment}' does not match required pattern '${segmentPattern}'.`);
19
+ }
20
+ if (reservedNames.includes(segment.toUpperCase())) {
21
+ throw new InvalidArgumentError(`Segment '${segment}' is a reserved name.`);
22
+ }
23
+ if (segment.endsWith(".")) {
24
+ throw new InvalidArgumentError(`Segment '${segment}' cannot end with a period.`);
25
+ }
26
+ }
27
+ let path = `${segments.join("/")}`;
28
+ if (!path.startsWith("/")) {
29
+ path = `/${path}`;
30
+ }
31
+ if (path.includes("//")) {
32
+ throw new InvalidArgumentError("Path cannot contain consecutive slashes.");
33
+ }
34
+ if (path.length > 400) {
35
+ throw new InvalidArgumentError("Path length exceeds 400 characters.");
36
+ }
37
+ return path;
38
+ }
39
+ export function driveItemRef(driveRef, itemId) {
40
+ if (!itemId) {
41
+ throw new ProtocolError("ItemID is missing");
42
+ }
43
+ return {
44
+ siteId: driveRef.siteId,
45
+ driveId: driveRef.driveId,
46
+ itemId
47
+ };
48
+ }
@@ -0,0 +1,17 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import InvalidArgumentError from "../errors/InvalidArgumentError.js";
3
+ import { driveItemPath } from "./driveItem.js";
4
+ describe("driveItemPath", () => {
5
+ it("throws an error for empty segment", () => {
6
+ expect(() => driveItemPath("")).toThrow(InvalidArgumentError);
7
+ });
8
+ it.each([
9
+ [["a"], "/a"],
10
+ [["a", "b"], "/a/b"],
11
+ [["a", "b", "c.txt"], "/a/b/c.txt"],
12
+ [["/a"], "/a"],
13
+ [[], "/"],
14
+ ])("parses %s", (segments, expected) => {
15
+ expect(driveItemPath(...segments)).toEqual(expected);
16
+ });
17
+ });
@@ -0,0 +1,9 @@
1
+ import { HttpProxyAgent } from "http-proxy-agent";
2
+ import { httpProxyEnv } from "./configuration.js";
3
+ export function getHttpAgent() {
4
+ const httpProxy = httpProxyEnv();
5
+ if (!httpProxy) {
6
+ return undefined;
7
+ }
8
+ return new HttpProxyAgent(httpProxy);
9
+ }
@@ -0,0 +1,3 @@
1
+ export function isHttpOk(status) {
2
+ return status >= 200 && status < 300;
3
+ }
@@ -0,0 +1,6 @@
1
+ export function operationIndexToId(index) {
2
+ return index.toString();
3
+ }
4
+ export function operationIdToIndex(id) {
5
+ return Number.parseInt(id, 10);
6
+ }
@@ -0,0 +1,24 @@
1
+ import InvalidArgumentError from "../errors/InvalidArgumentError.js";
2
+ /** Get the site name and item path from a given SharePoint document URL. (ie https://msftfuturesecureai.sharepoint.com/sites/FSAI-MQG/_layouts/15/doc.aspx?sourcedoc={500ff055-1e0f-4c2e-8b32-d167fba4778b} */
3
+ export function parseSharepointUrl(url) {
4
+ if (!url.hostname.endsWith(".sharepoint.com")) {
5
+ throw new InvalidArgumentError("Invalid SharePoint URL. Hostname must end with '.sharepoint.com'.");
6
+ }
7
+ const hostName = url.hostname;
8
+ const pathSegments = url.pathname.split("/");
9
+ const siteNameIndex = pathSegments.indexOf("sites") + 1;
10
+ if (siteNameIndex <= 0 || !pathSegments[siteNameIndex]) {
11
+ throw new InvalidArgumentError("Invalid SharePoint URL. Site name not found.");
12
+ }
13
+ const siteName = pathSegments[siteNameIndex];
14
+ const sourcedoc = url.searchParams.get("sourcedoc");
15
+ if (!sourcedoc) {
16
+ throw new InvalidArgumentError("Invalid SharePoint URL. Path not found in parameters.");
17
+ }
18
+ const itemId = sourcedoc.replace(/[{}]/g, "").toLocaleUpperCase();
19
+ return {
20
+ hostName,
21
+ siteName,
22
+ itemId,
23
+ };
24
+ }
@@ -0,0 +1,32 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { parseSharepointUrl } from "./sharepointUrl.js";
3
+ describe("parseSharepointUrl", () => {
4
+ it.each([
5
+ [
6
+ "https://msftfuturesecureai.sharepoint.com/sites/FSAI-MQG/_layouts/15/doc.aspx?sourcedoc={500ff055-1e0f-4c2e-8b32-d167fba4778b}&action=edit",
7
+ {
8
+ hostName: "msftfuturesecureai.sharepoint.com",
9
+ siteName: "FSAI-MQG",
10
+ itemId: "500FF055-1E0F-4C2E-8B32-D167FBA4778B"
11
+ }
12
+ ],
13
+ [
14
+ "https://msftfuturesecureai.sharepoint.com/:x:/r/sites/FSAI-MQG/_layouts/15/Doc.aspx?sourcedoc=%7B500FF055-1E0F-4C2E-8B32-D167FBA4778B%7D&file=Book.xlsx&action=default&mobileredirect=true",
15
+ {
16
+ hostName: "msftfuturesecureai.sharepoint.com",
17
+ siteName: "FSAI-MQG",
18
+ itemId: "500FF055-1E0F-4C2E-8B32-D167FBA4778B"
19
+ }
20
+ ],
21
+ [
22
+ "https://msftfuturesecureai.sharepoint.com/:x:/r/sites/FSAI-MQG/_layouts/15/Doc.aspx?sourcedoc=%7B418801C2-AF81-44F9-AD17-324A973AA6AF%7D&file=a.xlsx&action=default&mobileredirect=true",
23
+ {
24
+ hostName: "msftfuturesecureai.sharepoint.com",
25
+ siteName: "FSAI-MQG",
26
+ itemId: "418801C2-AF81-44F9-AD17-324A973AA6AF"
27
+ }
28
+ ],
29
+ ])("parses %s", (url, expected) => {
30
+ expect(parseSharepointUrl(new URL(url))).toEqual(expected);
31
+ });
32
+ });
@@ -0,0 +1,15 @@
1
+ import ProtocolError from "../errors/ProtocolError.js";
2
+ import { defaultSiteIdEnv } from "./configuration.js";
3
+ export function siteRef(siteId) {
4
+ if (!siteId) {
5
+ throw new ProtocolError("SiteID is missing");
6
+ }
7
+ return {
8
+ siteId
9
+ };
10
+ }
11
+ export function getDefaultSiteRef() {
12
+ return {
13
+ siteId: defaultSiteIdEnv(),
14
+ };
15
+ }
@@ -0,0 +1,3 @@
1
+ export function sleep(ms) {
2
+ return new Promise(resolve => setTimeout(resolve, ms));
3
+ }
@@ -0,0 +1,5 @@
1
+ export function kebabToCamelCase(str) {
2
+ return str
3
+ .replace(/^-+|-+$/g, "") // Remove leading and trailing dashes
4
+ .replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
5
+ }
@@ -0,0 +1,16 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { kebabToCamelCase } from "./stringCaseConversion.js";
3
+ describe("kebabToCamelCase", () => {
4
+ it("should handle single word strings", () => {
5
+ expect(kebabToCamelCase("single")).toBe("single");
6
+ });
7
+ it("should handle empty strings", () => {
8
+ expect(kebabToCamelCase("")).toBe("");
9
+ });
10
+ it("should handle strings with multiple dashes", () => {
11
+ expect(kebabToCamelCase("multiple-dashes-in-string")).toBe("multipleDashesInString");
12
+ });
13
+ it("should handle strings with leading and trailing dashes", () => {
14
+ expect(kebabToCamelCase("-leading-and-trailing-")).toBe("leadingAndTrailing");
15
+ });
16
+ });
@@ -0,0 +1,20 @@
1
+ import BadTemplateError from "../errors/BadTemplateError.js";
2
+ import { kebabToCamelCase } from "./stringCaseConversion.js";
3
+ const argmentPattern = /\{([a-z-]+)\}/g;
4
+ /** Create a GraphAPI path based on a given template and arguments. Escaping is automatically handeld */
5
+ export function generatePath(template, args) {
6
+ if (!template.startsWith("/")) {
7
+ throw new BadTemplateError(`Path template '${template}' must start with a slash.`);
8
+ }
9
+ if (template.includes("\n")) {
10
+ throw new BadTemplateError("Path template must not contain newlines.");
11
+ }
12
+ return template.replace(argmentPattern, (_, match) => {
13
+ const camelCaseKey = kebabToCamelCase(match);
14
+ const value = args[camelCaseKey];
15
+ if (value === undefined) {
16
+ throw new BadTemplateError(`Path template references argument '${camelCaseKey}' however no such argument provided.`);
17
+ }
18
+ return encodeURIComponent(value);
19
+ });
20
+ }
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import BadTemplateError from "../errors/BadTemplateError.js";
3
+ import { generatePath } from "./templatedPaths.js";
4
+ describe("generatePath", () => {
5
+ it("should generate a valid path with correct arguments", () => {
6
+ const template = "/me/drive/items/{item-id}/children";
7
+ const args = { itemId: "123" };
8
+ const result = generatePath(template, args);
9
+ expect(result).toBe("/me/drive/items/123/children");
10
+ });
11
+ it("should throw BadTemplateError if template does not start with a slash", () => {
12
+ const template = "me/drive/items/{item-id}/children";
13
+ const args = { itemId: "123" };
14
+ expect(() => generatePath(template, args)).toThrow(BadTemplateError);
15
+ });
16
+ it("should throw BadTemplateError if template contains newlines", () => {
17
+ const template = "/me/drive/items/{item-id}/children\n";
18
+ const args = { itemId: "123" };
19
+ expect(() => generatePath(template, args)).toThrow(BadTemplateError);
20
+ });
21
+ it("should throw BadTemplateError if argument is missing", () => {
22
+ const template = "/me/drive/items/{item-id}/children";
23
+ const args = {};
24
+ expect(() => generatePath(template, args)).toThrow(BadTemplateError);
25
+ });
26
+ it("should encode URI components", () => {
27
+ const template = "/me/drive/items/{item-id}/children";
28
+ const args = { itemId: "item name with spaces" };
29
+ const result = generatePath(template, args);
30
+ expect(result).toBe("/me/drive/items/item%20name%20with%20spaces/children");
31
+ });
32
+ it("should generate a valid path when the same argument is used twice", () => {
33
+ const template = "/me/drive/items/{item-id}/children/{item-id}";
34
+ const args = { itemId: "123" };
35
+ const result = generatePath(template, args);
36
+ expect(result).toBe("/me/drive/items/123/children/123");
37
+ });
38
+ it("should throw BadTemplateError if template has an argument that is not matched", () => {
39
+ const template = "/me/drive/items/{item-id}/children/{child-id}";
40
+ const args = { itemId: "123" };
41
+ expect(() => generatePath(template, args)).toThrow(BadTemplateError);
42
+ });
43
+ it("should generate a valid path with multiple arguments", () => {
44
+ const template = "/users/{user-id}/messages/{message-id}/attachments/{attachment-id}";
45
+ const args = { userId: "user123", messageId: "msg456", attachmentId: "att789" };
46
+ const result = generatePath(template, args);
47
+ expect(result).toBe("/users/user123/messages/msg456/attachments/att789");
48
+ });
49
+ });
@@ -0,0 +1,11 @@
1
+ export function generateTempFileName(extension = "tmp") {
2
+ const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
3
+ const charactersLength = characters.length;
4
+ const length = 16;
5
+ let result = '';
6
+ for (let i = 0; i < length; i++) {
7
+ const randomIndex = Math.floor(Math.random() * charactersLength);
8
+ result += characters[randomIndex];
9
+ }
10
+ return `~${result}.${extension}`;
11
+ }
@@ -0,0 +1,10 @@
1
+ export function workbookRangeRef(worksheetRef, address) {
2
+ return {
3
+ siteId: worksheetRef.siteId,
4
+ driveId: worksheetRef.driveId,
5
+ itemId: worksheetRef.itemId,
6
+ sessionId: worksheetRef.sessionId,
7
+ worksheetId: worksheetRef.worksheetId,
8
+ address: address
9
+ };
10
+ }
@@ -0,0 +1,8 @@
1
+ import InvalidArgumentError from "../errors/InvalidArgumentError.js";
2
+ const addressPattern = /^[A-Z]+\d+(:[A-Z]+\d+)?$/;
3
+ export function workbookRangeAddress(address) {
4
+ if (!addressPattern.test(address)) {
5
+ throw new InvalidArgumentError(`Invalid address format. It must match the pattern ${addressPattern}.`);
6
+ }
7
+ return address;
8
+ }