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,116 @@
1
+ import fetch from 'node-fetch';
2
+ import InvalidArgumentError from "./errors/InvalidArgumentError.js";
3
+ import RequestFailedError from "./errors/RequestFailedError.js";
4
+ import { getCurrentAccessToken } from "./services/accessToken.js";
5
+ import { getHttpAgent } from "./services/httpAgent.js";
6
+ import { operationIdToIndex, operationIndexToId } from "./services/operationId.js";
7
+ export const authenticationScope = "https://graph.microsoft.com/.default";
8
+ export const endpoint = "https://graph.microsoft.com/v1.0";
9
+ export const batchEndpoint = `${endpoint}/$batch`;
10
+ const maxBatchOperations = 20; // https://learn.microsoft.com/en-us/graph/json-batching?tabs=http#batch-size-limitations
11
+ export function operation(definition) {
12
+ // The returned operation can be called directly by simply `await`ing it, or it can be passed to the `parallel` or `sequential` functions to be executed in a batch.
13
+ const op = single(definition);
14
+ op.definition = definition;
15
+ return op;
16
+ }
17
+ async function single(definition) {
18
+ const accessToken = await getCurrentAccessToken(authenticationScope);
19
+ const agent = getHttpAgent();
20
+ const requestHeaders = {
21
+ "authorization": `Bearer ${accessToken}`,
22
+ ...Object.fromEntries(Object.entries(definition.headers ?? {}).filter(([_, v]) => v !== undefined)), // TODO: Tidy
23
+ };
24
+ const reply = await fetch(`${endpoint}${definition.path}`, {
25
+ method: definition.method,
26
+ headers: requestHeaders,
27
+ body: definition.body === null ? null : JSON.stringify(definition.body),
28
+ agent
29
+ });
30
+ const replyContentType = reply.headers.get('content-type')?.toLowerCase();
31
+ const body = replyContentType?.startsWith("application/json") ? await reply.json() : null;
32
+ RequestFailedError.throwIfNotOkOperation(reply.status, 0, definition, body);
33
+ return definition.responseTransform(body);
34
+ }
35
+ /** Execute a batch of GraphAPI operations in parallel. Provides the best performance for batch operations, however only useful if operations can logically be performed at the same time. */
36
+ export async function parallel(...ops) {
37
+ const definitions = ops.map(op => op.definition);
38
+ return await execute(...definitions);
39
+ }
40
+ /** Execute a batch of GraphAPI operations sequentially. */
41
+ export async function sequential(...ops) {
42
+ const definitions = ops.map(op => op.definition);
43
+ const sequentialOps = definitions.map((definition, index) => ({
44
+ ...definition,
45
+ dependsOn: index > 0 ? [index - 1] : undefined // Each op is dependant on the previous op
46
+ }));
47
+ return await execute(...sequentialOps);
48
+ }
49
+ async function execute(...ops) {
50
+ InvalidArgumentError.throwIfGreater(ops.length, maxBatchOperations, `At most ${maxBatchOperations} operations allowed, but ${ops.length} were provided.`);
51
+ if (ops.length === 0) {
52
+ return [];
53
+ }
54
+ const requestPayload = await composeRequestPayload(ops);
55
+ const reply = await fetch(batchEndpoint, requestPayload);
56
+ const replyPayload = await reply.json();
57
+ RequestFailedError.throwIfNotOkBatch(reply.status, ops, replyPayload);
58
+ const responses = parseResponses(replyPayload, ops);
59
+ return responses;
60
+ }
61
+ async function composeRequestPayload(ops) {
62
+ const accessToken = await getCurrentAccessToken(authenticationScope);
63
+ const agent = await getHttpAgent();
64
+ const requestBody = {
65
+ requests: ops.map((op, index) => ({
66
+ id: operationIndexToId(index),
67
+ method: op.method,
68
+ url: op.path,
69
+ headers: op.headers,
70
+ body: op.body === null ? undefined : op.body,
71
+ dependsOn: op.dependsOn?.map((id) => id.toString())
72
+ }))
73
+ };
74
+ const requestPayload = {
75
+ method: "POST",
76
+ headers: {
77
+ "authorization": `Bearer ${accessToken}`,
78
+ "accept": "application/json",
79
+ "content-type": "application/json"
80
+ },
81
+ body: JSON.stringify(requestBody),
82
+ agent
83
+ };
84
+ return requestPayload;
85
+ }
86
+ function normalizeBody(contentType, body) {
87
+ if (contentType?.startsWith("application/json") && typeof body === "string") {
88
+ return JSON.parse(atob(body));
89
+ }
90
+ return body;
91
+ }
92
+ function normalizeHeaders(input) {
93
+ const headers = {};
94
+ for (const key in input) {
95
+ if (input[key]) {
96
+ headers[key.toLocaleLowerCase()] = input[key];
97
+ }
98
+ }
99
+ return headers;
100
+ }
101
+ function parseResponses(replyPayload, ops) {
102
+ const results = [];
103
+ for (const response of replyPayload.responses) {
104
+ const index = operationIdToIndex(response.id);
105
+ const headers = normalizeHeaders(response.headers);
106
+ const contentType = headers["content-type"];
107
+ const body = normalizeBody(contentType, response.body);
108
+ const op = ops[index];
109
+ if (!op) {
110
+ throw new Error("Op not found. Should be impossible");
111
+ }
112
+ RequestFailedError.throwIfNotOkOperation(response.status, index, op, body);
113
+ results[index] = op.responseTransform(body);
114
+ }
115
+ return results; // TODO: Is there a neater way to massage the types correctly? This is functionally correct, but I do want to avoid using `unknown` here if possible.
116
+ }
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ // This file exists to satisfy npm "main"
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ import { operation } from "../../graphApi.js";
2
+ import { driveItemRef } from "../../services/driveItem.js";
3
+ import { generatePath } from "../../services/templatedPaths.js";
4
+ /** Create folder if it doesn't exist, and return the folder. Use a `DriveRef` to create in root or `DriveItemRef` to create in a subfolder. @see https://learn.microsoft.com/en-us/graph/api/driveitem-post-children */
5
+ export default function createFolder(parentRef, folderName) {
6
+ const pathSegment = parentRef.itemId ? "items/{item-id}" : "root";
7
+ return operation({
8
+ method: "POST",
9
+ path: generatePath(`/sites/{site-id}/drives/{drive-id}/${pathSegment}/children`, parentRef),
10
+ headers: {
11
+ "content-type": "application/json",
12
+ },
13
+ body: {
14
+ name: folderName,
15
+ folder: {},
16
+ "@microsoft.graph.conflictBehavior": "rename", // Do nothing if already exists
17
+ },
18
+ responseTransform: response => {
19
+ const item = response;
20
+ const itemRef = driveItemRef(parentRef, item.id);
21
+ return {
22
+ ...item,
23
+ ...itemRef
24
+ };
25
+ }
26
+ });
27
+ }
@@ -0,0 +1,31 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getDefaultDriveRef } from "../../services/drive.js";
3
+ import { generateTempFileName } from "../../services/temporaryFiles.js";
4
+ import deleteDriveItem from "../driveItem/deleteDriveItem.js";
5
+ import createFolder from "./createFolder.js";
6
+ describe("createFolder", () => {
7
+ it("can create root folder", { timeout: 10000 }, async () => {
8
+ const driveRef = getDefaultDriveRef();
9
+ const folderName = generateTempFileName();
10
+ const folder = await createFolder(driveRef, folderName);
11
+ expect(folder.webUrl?.endsWith(`/${folderName}`)).toBeTruthy();
12
+ await deleteDriveItem(folder);
13
+ });
14
+ it("can create sub-folder", { timeout: 10000 }, async () => {
15
+ const driveRef = getDefaultDriveRef();
16
+ const parentFolderName = generateTempFileName("parent");
17
+ console.debug(`Creating parent folder ${parentFolderName}...`);
18
+ const parentFolder = await createFolder(driveRef, parentFolderName);
19
+ console.debug(`Created parent folder ${parentFolder.webUrl}...`);
20
+ try {
21
+ const childFolderName = generateTempFileName("child");
22
+ console.debug(`Creating child folder ${childFolderName}...`);
23
+ const childFolder = await createFolder(parentFolder, childFolderName);
24
+ console.debug(`Created child folder ${childFolder.webUrl}...`);
25
+ expect(childFolder.webUrl).contains(`/${parentFolderName}/${childFolderName}`);
26
+ }
27
+ finally {
28
+ await deleteDriveItem(parentFolder);
29
+ }
30
+ });
31
+ });
@@ -0,0 +1,23 @@
1
+ import { operation } from "../../graphApi.js";
2
+ import { driveRef } from "../../services/drive.js";
3
+ import { generatePath } from "../../services/templatedPaths.js";
4
+ import { getDefaultSiteRef } from "../../services/site.js";
5
+ /** Retrieve the list of Drive resources available for a Site. @see https://learn.microsoft.com/en-us/graph/api/drive-list */
6
+ export default function listDrives(siteRef = getDefaultSiteRef()) {
7
+ return operation({
8
+ method: "GET",
9
+ path: generatePath("/sites/{site-id}/drives", siteRef),
10
+ headers: {},
11
+ body: null,
12
+ responseTransform: response => {
13
+ const list = response;
14
+ return list.value.map(drive => {
15
+ const ref = driveRef(siteRef, drive.id);
16
+ return {
17
+ ...drive,
18
+ ...ref
19
+ };
20
+ });
21
+ }
22
+ });
23
+ }
@@ -0,0 +1,9 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import listDrives from "./listDrives.js";
3
+ describe("listDrives", () => {
4
+ it("can listDrives", async () => {
5
+ const drives = await listDrives();
6
+ console.debug("Drives:", drives.map((drive) => [drive.id, drive.name]));
7
+ expect(drives.length).toBeGreaterThan(0);
8
+ });
9
+ });
@@ -0,0 +1,21 @@
1
+ import { operation } from "../../graphApi.js";
2
+ import { generatePath } from "../../services/templatedPaths.js";
3
+ /** Initiate an asynchronous copy of an item. NOTE: The copied file may not be immediately available and polling is required. @see https://learn.microsoft.com/en-us/graph/api/driveitem-copy */
4
+ export default function copyDriveItem(srcFileRef, dstFolderRef, dstFileName) {
5
+ return operation({
6
+ method: "POST",
7
+ path: generatePath("/sites/{site-id}/drives/{drive-id}/items/{item-id}/copy", srcFileRef),
8
+ headers: {
9
+ "content-type": "application/json",
10
+ },
11
+ body: {
12
+ name: dstFileName,
13
+ parentReference: {
14
+ siteId: dstFolderRef.siteId,
15
+ driveId: dstFolderRef.driveId,
16
+ id: dstFolderRef.itemId,
17
+ },
18
+ },
19
+ responseTransform: () => undefined
20
+ });
21
+ }
@@ -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 createFolder from "../drive/createFolder.js";
7
+ import copyDriveItem from "./copyDriveItem.js";
8
+ import getDriveItemByPath from "./getDriveItemByPath.js";
9
+ describe("copyDriveItem", () => {
10
+ it("can copy an item to a new folder", { timeout: 10000 }, async () => {
11
+ const driveRef = getDefaultDriveRef();
12
+ const srcFolderName = generateTempFileName();
13
+ const srcFolder = await createFolder(driveRef, srcFolderName);
14
+ const dstFolderName = generateTempFileName();
15
+ const dstFolder = await createFolder(driveRef, dstFolderName);
16
+ try {
17
+ const copiedItemName = `${srcFolderName}-copy`;
18
+ await copyDriveItem(srcFolder, dstFolder, copiedItemName);
19
+ const copyPath = driveItemPath(dstFolderName, copiedItemName);
20
+ const copyFolder = await getDriveItemByPath(driveRef, copyPath);
21
+ expect(copyFolder.webUrl?.endsWith(copyPath)).toBeTruthy();
22
+ }
23
+ finally {
24
+ await deleteDriveItemWithRetry(srcFolder);
25
+ await deleteDriveItemWithRetry(dstFolder);
26
+ }
27
+ });
28
+ });
@@ -0,0 +1,12 @@
1
+ import { operation } from "../../graphApi.js";
2
+ import { generatePath } from "../../services/templatedPaths.js";
3
+ /** Delete an item. @see https://learn.microsoft.com/en-us/graph/api/driveitem-delete */
4
+ export default function deleteDriveItem(itemRef) {
5
+ return operation({
6
+ method: "DELETE",
7
+ path: generatePath("/sites/{site-id}/drives/{drive-id}/items/{item-id}", itemRef),
8
+ headers: {},
9
+ body: null,
10
+ responseTransform: () => undefined
11
+ });
12
+ }
@@ -0,0 +1,22 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getDefaultDriveRef } from "../../services/drive.js";
3
+ import { generateTempFileName } from "../../services/temporaryFiles.js";
4
+ import createFolder from "../drive/createFolder.js";
5
+ import deleteDriveItem from "./deleteDriveItem.js";
6
+ import getDriveItem from "./getDriveItem.js";
7
+ describe("deleteDriveItem", () => {
8
+ it("can delete an existing folder", { timeout: 10000 }, async () => {
9
+ const folderName = generateTempFileName();
10
+ const folder = await createFolder(getDefaultDriveRef(), folderName);
11
+ await deleteDriveItem(folder);
12
+ await expect(getDriveItem(folder)).rejects.toThrow();
13
+ });
14
+ it("throws an error when trying to delete a non-existent item", async () => {
15
+ const driveRef = getDefaultDriveRef();
16
+ const nonExistentItemRef = {
17
+ ...driveRef,
18
+ itemId: "non-existent-item-id"
19
+ };
20
+ await expect(deleteDriveItem(nonExistentItemRef)).rejects.toThrow();
21
+ });
22
+ });
@@ -0,0 +1,18 @@
1
+ import { operation } from "../../graphApi.js";
2
+ import { generatePath } from "../../services/templatedPaths.js";
3
+ /** Retrieve the metadata for an item in a drive. @see https://learn.microsoft.com/en-us/graph/api/driveitem-get */
4
+ export default function getDriveItem(itemRef) {
5
+ return operation({
6
+ method: "GET",
7
+ path: generatePath("/sites/{site-id}/drives/{drive-id}/items/{item-id}", itemRef),
8
+ headers: {},
9
+ body: null,
10
+ responseTransform: response => {
11
+ const item = response;
12
+ return {
13
+ ...item,
14
+ ...itemRef
15
+ };
16
+ }
17
+ });
18
+ }
@@ -0,0 +1,24 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getDefaultDriveRef } from "../../services/drive.js";
3
+ import { generateTempFileName } from "../../services/temporaryFiles.js";
4
+ import deleteDriveItemWithRetry from "../../tasks/deleteDriveItemWithRetry.js";
5
+ import createFolder from "../drive/createFolder.js";
6
+ import getDriveItem from "./getDriveItem.js";
7
+ describe("getDriveItem", () => {
8
+ const defaultDriveRef = getDefaultDriveRef();
9
+ it("can retrieve an existing folder", { timeout: 10000 }, async () => {
10
+ const folderName = generateTempFileName();
11
+ const folder = await createFolder(defaultDriveRef, folderName);
12
+ const retrievedFolder = await getDriveItem(folder);
13
+ expect(retrievedFolder.id).toBe(folder.id);
14
+ expect(retrievedFolder.name).toBe(folderName);
15
+ await deleteDriveItemWithRetry(folder);
16
+ });
17
+ it("throws an error when trying to retrieve a non-existent item", async () => {
18
+ const nonExistentItemRef = {
19
+ ...defaultDriveRef,
20
+ itemId: "non-existent-item-id"
21
+ };
22
+ await expect(getDriveItem(nonExistentItemRef)).rejects.toThrow();
23
+ });
24
+ });
@@ -0,0 +1,20 @@
1
+ import { operation } from "../../graphApi.js";
2
+ import { driveItemRef } from "../../services/driveItem.js";
3
+ import { generatePath } from "../../services/templatedPaths.js";
4
+ /** Retrieve the metadata for an item in a drive by file path. If the target file is moved this will cease working. @see https://learn.microsoft.com/en-us/graph/api/driveitem-get */
5
+ export default function getDriveItemByPath(driveRef, itemPath) {
6
+ return operation({
7
+ method: "GET",
8
+ path: generatePath(`/sites/{site-id}/drives/{drive-id}/root:${itemPath}`, driveRef),
9
+ headers: {},
10
+ body: null,
11
+ responseTransform: response => {
12
+ const item = response;
13
+ const itemRef = driveItemRef(driveRef, item.id);
14
+ return {
15
+ ...item,
16
+ ...itemRef
17
+ };
18
+ }
19
+ });
20
+ }
@@ -0,0 +1,22 @@
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 createFolder from "../drive/createFolder.js";
7
+ import getDriveItemByPath from "./getDriveItemByPath.js";
8
+ describe("getDriveItemByPath", () => {
9
+ it("can retrieve an existing folder by path", { timeout: 10000 }, async () => {
10
+ const folderName = generateTempFileName();
11
+ const folder = await createFolder(getDefaultDriveRef(), folderName);
12
+ const folderPath = driveItemPath(folderName);
13
+ const retrievedFolder = await getDriveItemByPath(getDefaultDriveRef(), folderPath);
14
+ expect(retrievedFolder.id).toBe(folder.id);
15
+ expect(retrievedFolder.name).toBe(folderName);
16
+ await deleteDriveItemWithRetry(folder);
17
+ });
18
+ it("throws an error when trying to retrieve a non-existent item by path", async () => {
19
+ const nonExistentItemPath = driveItemPath("non-existent-item-path");
20
+ await expect(getDriveItemByPath(getDefaultDriveRef(), nonExistentItemPath)).rejects.toThrow();
21
+ });
22
+ });
@@ -0,0 +1,22 @@
1
+ import fetch from 'node-fetch';
2
+ import { authenticationScope, endpoint } from "../../graphApi.js";
3
+ import { getCurrentAccessToken } from "../../services/accessToken.js";
4
+ import { getHttpAgent } from "../../services/httpAgent.js";
5
+ import { generatePath } from "../../services/templatedPaths.js";
6
+ /** Download drive item. @see https://learn.microsoft.com/en-us/graph/api/driveitem-get-content */
7
+ export default async function getDriveItemContent(itemRef) {
8
+ // Note this method doesn't match the standard pattern since the batching library doesn't support non-JSON return types, and there appears to be no value in adding support.
9
+ const url = `${endpoint}${generatePath("/sites/{site-id}/drives/{drive-id}/items/{item-id}/content", itemRef)}`;
10
+ const accessToken = await getCurrentAccessToken(authenticationScope);
11
+ const agent = getHttpAgent();
12
+ const response = await fetch(url, {
13
+ headers: {
14
+ "authorization": `Bearer ${accessToken}`
15
+ },
16
+ agent
17
+ });
18
+ if (!response.ok) {
19
+ throw new Error(`Failed to download file: ${response.status} ${response.statusText}`);
20
+ }
21
+ return await response.arrayBuffer();
22
+ }
@@ -0,0 +1,37 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getDefaultDriveRef } from "../../services/drive.js";
3
+ import { driveItemPath, driveItemRef } 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 calculateWorkbook from "../workbook/calculateWorkbook.js";
9
+ import createWorkbook from "../workbook/createWorkbook.js";
10
+ import updateWorkbookRange from "../workbookRange/updateWorkbookRange.js";
11
+ import getDriveItemContent from "./getDriveItemContent.js";
12
+ describe("getDriveItemContent", () => {
13
+ it("can download the content of an existing workbook", { timeout: 20000 }, async () => {
14
+ const workbookPath = driveItemPath(generateTempFileName("xlsx"));
15
+ const workbook = await createWorkbook(getDefaultDriveRef(), workbookPath);
16
+ try {
17
+ const worksheetRef = workbookWorksheetRef(workbook, defaultWorkbookWorksheetId);
18
+ const rangeRef = workbookRangeRef(worksheetRef, "A1:B1");
19
+ await updateWorkbookRange(rangeRef, {
20
+ values: [
21
+ ["Hello", "World"]
22
+ ]
23
+ });
24
+ await calculateWorkbook(workbook);
25
+ const content = await getDriveItemContent(workbook);
26
+ expect(content).toBeInstanceOf(ArrayBuffer);
27
+ expect(content.byteLength).toBeGreaterThan(0);
28
+ }
29
+ finally {
30
+ await deleteDriveItemWithRetry(workbook);
31
+ }
32
+ });
33
+ it("throws an error when trying to download a non-existent item", async () => {
34
+ const nonExistentItemRef = driveItemRef(getDefaultDriveRef(), "non-existent-item-id");
35
+ await expect(getDriveItemContent(nonExistentItemRef)).rejects.toThrow();
36
+ });
37
+ });
@@ -0,0 +1,25 @@
1
+ import { operation } from "../../graphApi.js";
2
+ import { getDefaultDriveRef } from "../../services/drive.js";
3
+ import { driveItemRef } from "../../services/driveItem.js";
4
+ import { generatePath } from "../../services/templatedPaths.js";
5
+ /** Retrieve the metadata for items in a drive by file path. Use a `DriveRef` to reference a drive root or `DriveItemRef` for a subfolder. @see https://learn.microsoft.com/en-us/graph/api/driveitem-list-children */
6
+ export default function listDriveItems(parentRef = getDefaultDriveRef()) {
7
+ const pathSegment = parentRef.itemId ? "items/{item-id}" : "root";
8
+ return operation({
9
+ method: "GET",
10
+ path: generatePath(`/sites/{site-id}/drives/{drive-id}/${pathSegment}/children`, parentRef),
11
+ headers: {},
12
+ body: null,
13
+ responseTransform: response => {
14
+ const list = response;
15
+ const items = list.value.map(item => {
16
+ const itemRef = driveItemRef(parentRef, item.id);
17
+ return {
18
+ ...item,
19
+ ...itemRef,
20
+ };
21
+ });
22
+ return items;
23
+ }
24
+ });
25
+ }
@@ -0,0 +1,24 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getDefaultDriveRef } from "../../services/drive.js";
3
+ import { generateTempFileName } from "../../services/temporaryFiles.js";
4
+ import deleteDriveItemWithRetry from "../../tasks/deleteDriveItemWithRetry.js";
5
+ import createFolder from "../drive/createFolder.js";
6
+ import listDriveItems from "./listDriveItems.js";
7
+ describe("listDriveItems", () => {
8
+ it("can list items in the root folder", { timeout: 10000 }, async () => {
9
+ const driveRef = getDefaultDriveRef();
10
+ const items = await listDriveItems(driveRef);
11
+ expect(items).toBeInstanceOf(Array);
12
+ });
13
+ it("can list items in a folder", async () => {
14
+ const driveRef = getDefaultDriveRef();
15
+ const folder = await createFolder(driveRef, generateTempFileName());
16
+ try {
17
+ const items = await listDriveItems(folder);
18
+ expect(items).toBeInstanceOf(Array);
19
+ }
20
+ finally {
21
+ await deleteDriveItemWithRetry(folder);
22
+ }
23
+ });
24
+ });
@@ -0,0 +1,18 @@
1
+ import { operation } from "../../graphApi.js";
2
+ import { generatePath } from "../../services/templatedPaths.js";
3
+ /** Retrieve properties for a site resource. @see https://learn.microsoft.com/en-us/graph/api/site-get */
4
+ export default function getSite(siteRef) {
5
+ return operation({
6
+ method: "GET",
7
+ path: generatePath("/sites/{site-id}", siteRef),
8
+ headers: {},
9
+ body: null,
10
+ responseTransform: response => {
11
+ const site = response;
12
+ return {
13
+ ...site,
14
+ ...siteRef
15
+ };
16
+ }
17
+ });
18
+ }
@@ -0,0 +1,15 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getDefaultSiteRef, siteRef } from "../../services/site.js";
3
+ import getSite from "./getSite.js";
4
+ describe("getSite", () => {
5
+ it("can retrieve an existing site", async () => {
6
+ const defaultSiteRef = getDefaultSiteRef();
7
+ const site = await getSite(defaultSiteRef);
8
+ expect(site.id).toBeDefined();
9
+ expect(site.name).toBeDefined();
10
+ });
11
+ it("throws an error when trying to retrieve a non-existent site", async () => {
12
+ const nonExistentSiteRef = siteRef("non-existent-site-id");
13
+ await expect(getSite(nonExistentSiteRef)).rejects.toThrow();
14
+ });
15
+ });