@openfn/language-msgraph 0.3.4 → 0.4.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.
package/ast.json CHANGED
@@ -326,6 +326,135 @@
326
326
  ]
327
327
  },
328
328
  "valid": true
329
+ },
330
+ {
331
+ "name": "uploadFile",
332
+ "params": [
333
+ "resource",
334
+ "data",
335
+ "callback"
336
+ ],
337
+ "docs": {
338
+ "description": "Upload a file to a drive",
339
+ "tags": [
340
+ {
341
+ "title": "public",
342
+ "description": null,
343
+ "type": null
344
+ },
345
+ {
346
+ "title": "example",
347
+ "description": "uploadFile(\n state => ({\n driveId: state.driveId,\n folderId: state.folderId,\n fileName: `Tracker.xlsx`,\n }),\n state => state.buffer\n);",
348
+ "caption": "Upload Excel file to a drive using `driveId` and `parantItemId`"
349
+ },
350
+ {
351
+ "title": "example",
352
+ "description": "uploadFile(\n state => ({\n siteId: state.siteId,\n folderId: state.folderId,\n fileName: `Report.xlsx`,\n }),\n state => state.buffer\n);",
353
+ "caption": "Upload Excel file to a SharePoint drive using `siteId` and `parantItemId`"
354
+ },
355
+ {
356
+ "title": "function",
357
+ "description": null,
358
+ "name": null
359
+ },
360
+ {
361
+ "title": "param",
362
+ "description": "Resource Object",
363
+ "type": {
364
+ "type": "NameExpression",
365
+ "name": "Object"
366
+ },
367
+ "name": "resource"
368
+ },
369
+ {
370
+ "title": "param",
371
+ "description": "Drive Id",
372
+ "type": {
373
+ "type": "OptionalType",
374
+ "expression": {
375
+ "type": "NameExpression",
376
+ "name": "String"
377
+ }
378
+ },
379
+ "name": "resource.driveId"
380
+ },
381
+ {
382
+ "title": "param",
383
+ "description": "Site Id",
384
+ "type": {
385
+ "type": "OptionalType",
386
+ "expression": {
387
+ "type": "NameExpression",
388
+ "name": "String"
389
+ }
390
+ },
391
+ "name": "resource.driveId"
392
+ },
393
+ {
394
+ "title": "param",
395
+ "description": "Parent folder id",
396
+ "type": {
397
+ "type": "OptionalType",
398
+ "expression": {
399
+ "type": "NameExpression",
400
+ "name": "String"
401
+ }
402
+ },
403
+ "name": "resource.folderId"
404
+ },
405
+ {
406
+ "title": "param",
407
+ "description": "Resource content-type",
408
+ "type": {
409
+ "type": "OptionalType",
410
+ "expression": {
411
+ "type": "NameExpression",
412
+ "name": "String"
413
+ }
414
+ },
415
+ "name": "resource.contentType"
416
+ },
417
+ {
418
+ "title": "param",
419
+ "description": "Specify conflict behavior if file with the same name exists. Can be \"rename | fail | replace\"",
420
+ "type": {
421
+ "type": "OptionalType",
422
+ "expression": {
423
+ "type": "NameExpression",
424
+ "name": "String"
425
+ }
426
+ },
427
+ "name": "resource.onConflict"
428
+ },
429
+ {
430
+ "title": "param",
431
+ "description": "A buffer containing the file.",
432
+ "type": {
433
+ "type": "NameExpression",
434
+ "name": "Object"
435
+ },
436
+ "name": "data"
437
+ },
438
+ {
439
+ "title": "param",
440
+ "description": "Optional callback function",
441
+ "type": {
442
+ "type": "NameExpression",
443
+ "name": "Function"
444
+ },
445
+ "name": "callback"
446
+ },
447
+ {
448
+ "title": "returns",
449
+ "description": null,
450
+ "type": {
451
+ "type": "NameExpression",
452
+ "name": "Operation"
453
+ }
454
+ }
455
+ ]
456
+ },
457
+ "valid": false
329
458
  }
330
459
  ],
331
460
  "exports": [],
package/dist/index.cjs CHANGED
@@ -1,6 +1,8 @@
1
+ var __create = Object.create;
1
2
  var __defProp = Object.defineProperty;
2
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
4
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
5
7
  var __export = (target, all) => {
6
8
  for (var name in all)
@@ -14,6 +16,10 @@ var __copyProps = (to, from, except, desc) => {
14
16
  }
15
17
  return to;
16
18
  };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
21
+ mod
22
+ ));
17
23
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
24
 
19
25
  // src/index.js
@@ -37,7 +43,9 @@ __export(src_exports, {
37
43
  merge: () => import_language_common3.merge,
38
44
  parseCsv: () => import_language_common3.parseCsv,
39
45
  request: () => request,
40
- sourceValue: () => import_language_common3.sourceValue
46
+ sheetToBuffer: () => sheetToBuffer,
47
+ sourceValue: () => import_language_common3.sourceValue,
48
+ uploadFile: () => uploadFile
41
49
  });
42
50
  module.exports = __toCommonJS(src_exports);
43
51
 
@@ -61,15 +69,19 @@ __export(Adaptor_exports, {
61
69
  merge: () => import_language_common3.merge,
62
70
  parseCsv: () => import_language_common3.parseCsv,
63
71
  request: () => request,
64
- sourceValue: () => import_language_common3.sourceValue
72
+ sheetToBuffer: () => sheetToBuffer,
73
+ sourceValue: () => import_language_common3.sourceValue,
74
+ uploadFile: () => uploadFile
65
75
  });
66
76
  var import_language_common2 = require("@openfn/language-common");
67
- var import_util = require("@openfn/language-common/util");
77
+ var import_util2 = require("@openfn/language-common/util");
68
78
 
69
79
  // src/Utils.js
80
+ var import_xlsx = __toESM(require("xlsx"), 1);
70
81
  var import_undici = require("undici");
71
82
  var import_node_stream = require("stream");
72
83
  var import_language_common = require("@openfn/language-common");
84
+ var import_util = require("@openfn/language-common/util");
73
85
  function assertDrive(state, driveName) {
74
86
  if (!state.drives[driveName]) {
75
87
  const errorString = [
@@ -80,7 +92,7 @@ function assertDrive(state, driveName) {
80
92
  throw new Error(errorString);
81
93
  }
82
94
  }
83
- function getUrl(resource, apiVersion) {
95
+ function setUrl(resource, apiVersion) {
84
96
  if (isValidHttpUrl(resource))
85
97
  return resource;
86
98
  const pathSuffix = apiVersion ? `${apiVersion}/${resource}` : `v1.0/${resource}`;
@@ -95,9 +107,6 @@ function isValidHttpUrl(string) {
95
107
  }
96
108
  return url.protocol === "http:" || url.protocol === "https:";
97
109
  }
98
- function getAuth(token) {
99
- return token ? { headers: { Authorization: `Bearer ${token}` } } : null;
100
- }
101
110
  var isStream = (value) => {
102
111
  if (value && typeof value == "object") {
103
112
  if (value instanceof import_node_stream.Readable || value instanceof import_node_stream.Writable) {
@@ -138,21 +147,22 @@ function handleResponseError(response, data, method) {
138
147
  throw new Error(errorString);
139
148
  }
140
149
  }
141
- var request = async (urlString, params = {}, method = "GET") => {
142
- let url = urlString;
143
- const defaultHeaders = { "Content-Type": "application/json" };
144
- const { headers, parseAs } = params;
145
- const setHeaders = { ...headers, ...defaultHeaders };
146
- delete params.parseAs;
147
- delete params.headers;
148
- let options = {
149
- method,
150
- headers: setHeaders
151
- };
152
- if ("GET" === method) {
153
- url = `${urlString}?${new URLSearchParams(params).toString()}`;
154
- } else {
155
- options.body = JSON.stringify(params);
150
+ var defaultOptions = {
151
+ method: "GET",
152
+ headers: {
153
+ "Content-Type": "application/json"
154
+ },
155
+ accessToken: ""
156
+ };
157
+ var request = async (path, params) => {
158
+ let url = path;
159
+ const options = { ...defaultOptions, ...params };
160
+ const { parseAs, query, method, accessToken } = options;
161
+ delete options.parseAs;
162
+ delete options.accessToken;
163
+ options.headers["Authorization"] = makeAuthHeader(accessToken);
164
+ if (method === "GET" && query) {
165
+ url = `${path}?${new URLSearchParams(query).toString()}`;
156
166
  }
157
167
  const response = await (0, import_undici.fetch)(url, options);
158
168
  const contentType = response.headers.get("Content-Type");
@@ -168,6 +178,41 @@ var request = async (urlString, params = {}, method = "GET") => {
168
178
  handleResponseError(response, data, method);
169
179
  return data;
170
180
  };
181
+ function makeAuthHeader(accessToken) {
182
+ return accessToken ? `Bearer ${accessToken}` : null;
183
+ }
184
+ var defaultSheetOptions = {
185
+ bookType: "xlsx",
186
+ wsName: "Sheet"
187
+ };
188
+ function sheetToBuffer(rows, options, callback) {
189
+ return (state) => {
190
+ const resolvedRows = (0, import_language_common.asData)(rows, state);
191
+ const [resolvedOptions] = (0, import_util.expandReferences)(state, options);
192
+ const { wsName, bookType } = {
193
+ ...defaultSheetOptions,
194
+ ...resolvedOptions
195
+ };
196
+ const workbook = import_xlsx.default.utils.book_new();
197
+ const worksheet = import_xlsx.default.utils.json_to_sheet(resolvedRows);
198
+ import_xlsx.default.utils.book_append_sheet(workbook, worksheet, wsName);
199
+ const buffer = import_xlsx.default.write(workbook, { type: "buffer", bookType });
200
+ console.log(`Creating sheet buffer with bookType '${bookType}'`);
201
+ const nextState = { ...state, buffer };
202
+ if (callback)
203
+ return callback(nextState);
204
+ return nextState;
205
+ };
206
+ }
207
+ function assertResources(resources) {
208
+ const { driveId, siteId, folderId } = resources;
209
+ if (driveId && siteId)
210
+ throw new Error('Use either "driveId" or "siteId" not both');
211
+ if (!driveId && !siteId)
212
+ throw new Error('"siteId" or "driveId" is required');
213
+ if (!folderId)
214
+ throw new Error("Parent Item Id is required");
215
+ }
171
216
 
172
217
  // src/Adaptor.js
173
218
  var import_language_common3 = require("@openfn/language-common");
@@ -178,8 +223,13 @@ function execute(...operations) {
178
223
  drives: {}
179
224
  };
180
225
  const cleanup = (finalState) => {
181
- const { drives, ...rest } = finalState;
182
- return rest;
226
+ if (finalState == null ? void 0 : finalState.buffer) {
227
+ delete finalState.buffer;
228
+ }
229
+ if (finalState == null ? void 0 : finalState.drives) {
230
+ delete finalState.drives;
231
+ }
232
+ return finalState;
183
233
  };
184
234
  return (state) => {
185
235
  return (0, import_language_common2.execute)(...operations)({
@@ -193,19 +243,19 @@ function execute(...operations) {
193
243
  }
194
244
  function create(resource, data, callback) {
195
245
  return (state) => {
196
- const [resolvedResource, resolvedData] = (0, import_util.expandReferences)(
246
+ const [resolvedResource, resolvedData] = (0, import_util2.expandReferences)(
197
247
  state,
198
248
  resource,
199
249
  data
200
250
  );
201
251
  const { accessToken, apiVersion } = state.configuration;
202
- const url = getUrl({ apiVersion, resolvedResource });
203
- const auth = getAuth(accessToken);
252
+ const url = setUrl({ apiVersion, resolvedResource });
204
253
  const options = {
205
- auth,
206
- ...resolvedData
254
+ accessToken,
255
+ body: JSON.stringify(resolvedData),
256
+ method: "POST"
207
257
  };
208
- return request(url, options, "POST").then(
258
+ return request(url, options).then(
209
259
  (response) => handleResponse(response, state, callback)
210
260
  );
211
261
  };
@@ -213,10 +263,9 @@ function create(resource, data, callback) {
213
263
  function get(path, query, callback = false) {
214
264
  return (state) => {
215
265
  const { accessToken, apiVersion } = state.configuration;
216
- const [resolvedPath, resolvedQuery] = (0, import_util.expandReferences)(state, path, query);
217
- const url = getUrl(resolvedPath, apiVersion);
218
- const auth = getAuth(accessToken);
219
- return request(url, { ...resolvedQuery, ...auth }).then(
266
+ const [resolvedPath, resolvedQuery] = (0, import_util2.expandReferences)(state, path, query);
267
+ const url = setUrl(resolvedPath, apiVersion);
268
+ return request(url, { query: resolvedQuery, accessToken }).then(
220
269
  (response) => handleResponse(response, state, callback)
221
270
  );
222
271
  };
@@ -224,7 +273,7 @@ function get(path, query, callback = false) {
224
273
  function getDrive(specifier, name = "default", callback = (s) => s) {
225
274
  return (state) => {
226
275
  const { accessToken, apiVersion } = state.configuration;
227
- const [resolvedSpecifier, resolvedName] = (0, import_util.expandReferences)(
276
+ const [resolvedSpecifier, resolvedName] = (0, import_util2.expandReferences)(
228
277
  state,
229
278
  specifier,
230
279
  name
@@ -236,9 +285,8 @@ function getDrive(specifier, name = "default", callback = (s) => s) {
236
285
  } else {
237
286
  resource = `${owner}/${id}/drive`;
238
287
  }
239
- const url = getUrl(resource, apiVersion);
240
- const auth = getAuth(accessToken);
241
- return request(url, { ...auth }).then((response) => {
288
+ const url = setUrl(resource, apiVersion);
289
+ return request(url, { accessToken }).then((response) => {
242
290
  state.drives[resolvedName] = response;
243
291
  return callback(state);
244
292
  });
@@ -246,17 +294,17 @@ function getDrive(specifier, name = "default", callback = (s) => s) {
246
294
  }
247
295
  function getFolder(pathOrId, options, callback = (s) => s) {
248
296
  return async (state) => {
249
- const defaultOptions = {
297
+ const defaultOptions2 = {
250
298
  driveName: "default",
251
299
  metadata: false
252
300
  };
253
301
  const { accessToken, apiVersion } = state.configuration;
254
- const [resolvedPathOrId, resolvedOptions] = (0, import_util.expandReferences)(
302
+ const [resolvedPathOrId, resolvedOptions] = (0, import_util2.expandReferences)(
255
303
  state,
256
304
  pathOrId,
257
305
  options
258
306
  );
259
- const { driveName, metadata } = { ...defaultOptions, ...resolvedOptions };
307
+ const { driveName, metadata } = { ...defaultOptions2, ...resolvedOptions };
260
308
  assertDrive(state, driveName);
261
309
  const { id: driveId } = state.drives[driveName];
262
310
  let resource;
@@ -270,27 +318,26 @@ function getFolder(pathOrId, options, callback = (s) => s) {
270
318
  if (!metadata) {
271
319
  resource += resolvedPathOrId.startsWith("/") ? ":/children" : "/children";
272
320
  }
273
- const url = getUrl(resource, apiVersion);
274
- const auth = getAuth(accessToken);
275
- return request(url, { ...auth }).then(
321
+ const url = setUrl(resource, apiVersion);
322
+ return request(url, { accessToken }).then(
276
323
  (response) => handleResponse(response, state, callback)
277
324
  );
278
325
  };
279
326
  }
280
327
  function getFile(pathOrId, options, callback = (s) => s) {
281
- const defaultOptions = {
328
+ const defaultOptions2 = {
282
329
  driveName: "default",
283
330
  metadata: false
284
331
  };
285
332
  return async (state) => {
286
333
  const { accessToken, apiVersion } = state.configuration;
287
- const [resolvedPathOrId, resolvedOptions] = (0, import_util.expandReferences)(
334
+ const [resolvedPathOrId, resolvedOptions] = (0, import_util2.expandReferences)(
288
335
  state,
289
336
  pathOrId,
290
337
  options
291
338
  );
292
339
  const { driveName, metadata } = {
293
- ...defaultOptions,
340
+ ...defaultOptions2,
294
341
  ...resolvedOptions
295
342
  };
296
343
  assertDrive(state, driveName);
@@ -306,15 +353,60 @@ function getFile(pathOrId, options, callback = (s) => s) {
306
353
  if (!metadata) {
307
354
  resource += resolvedPathOrId.startsWith("/") ? ":/content" : "/content";
308
355
  }
309
- const url = getUrl(resource, apiVersion);
310
- const auth = getAuth(accessToken);
356
+ const url = setUrl(resource, apiVersion);
311
357
  const response = await request(url, {
312
- ...auth,
358
+ accessToken,
313
359
  parseAs: metadata ? "json" : "text"
314
360
  });
315
361
  return handleResponse(response, state, callback);
316
362
  };
317
363
  }
364
+ var defaultResource = {
365
+ contentType: "application/octet-stream",
366
+ driveId: "",
367
+ folderId: "",
368
+ fileName: "sheet.xls",
369
+ onConflict: "replace"
370
+ };
371
+ function uploadFile(resource, data, callback) {
372
+ return async (state) => {
373
+ const { accessToken, apiVersion } = state.configuration;
374
+ const [resolvedResource, resolvedData] = (0, import_util2.expandReferences)(
375
+ state,
376
+ resource,
377
+ data
378
+ );
379
+ const { contentType, driveId, siteId, folderId, onConflict, fileName } = {
380
+ ...defaultResource,
381
+ ...resolvedResource
382
+ };
383
+ assertResources({ driveId, siteId, folderId });
384
+ const path = driveId && `drives/${driveId}/items/${folderId}:/${fileName}:/createUploadSession` || siteId && `sites/${siteId}/drive/items/${folderId}:/${fileName}:/createUploadSession`;
385
+ const uploadSession = await request(setUrl(path, apiVersion), {
386
+ method: "POST",
387
+ accessToken,
388
+ headers: {
389
+ "Content-Type": "application/json"
390
+ },
391
+ body: JSON.stringify({
392
+ "@microsoft.graph.conflictBehavior": onConflict,
393
+ name: fileName
394
+ })
395
+ });
396
+ const uploadUrl = uploadSession.uploadUrl;
397
+ console.log(`Uploading file...`);
398
+ return request(uploadUrl, {
399
+ method: "PUT",
400
+ accessToken,
401
+ headers: {
402
+ "Content-Type": contentType,
403
+ "Content-Length": `${resolvedData.length}`,
404
+ "Content-Range": `bytes 0-${resolvedData.length - 1}/${resolvedData.length}`
405
+ },
406
+ body: resolvedData
407
+ }).then((response) => handleResponse(response, state, callback));
408
+ };
409
+ }
318
410
 
319
411
  // src/index.js
320
412
  var src_default = Adaptor_exports;
@@ -337,5 +429,7 @@ var src_default = Adaptor_exports;
337
429
  merge,
338
430
  parseCsv,
339
431
  request,
340
- sourceValue
432
+ sheetToBuffer,
433
+ sourceValue,
434
+ uploadFile
341
435
  });
package/dist/index.js CHANGED
@@ -24,15 +24,19 @@ __export(Adaptor_exports, {
24
24
  merge: () => merge,
25
25
  parseCsv: () => parseCsv,
26
26
  request: () => request,
27
- sourceValue: () => sourceValue
27
+ sheetToBuffer: () => sheetToBuffer,
28
+ sourceValue: () => sourceValue,
29
+ uploadFile: () => uploadFile
28
30
  });
29
31
  import { execute as commonExecute } from "@openfn/language-common";
30
- import { expandReferences } from "@openfn/language-common/util";
32
+ import { expandReferences as expandReferences2 } from "@openfn/language-common/util";
31
33
 
32
34
  // src/Utils.js
35
+ import xlsx from "xlsx";
33
36
  import { fetch } from "undici";
34
37
  import { Readable, Writable } from "stream";
35
- import { composeNextState } from "@openfn/language-common";
38
+ import { composeNextState, asData } from "@openfn/language-common";
39
+ import { expandReferences } from "@openfn/language-common/util";
36
40
  function assertDrive(state, driveName) {
37
41
  if (!state.drives[driveName]) {
38
42
  const errorString = [
@@ -43,7 +47,7 @@ function assertDrive(state, driveName) {
43
47
  throw new Error(errorString);
44
48
  }
45
49
  }
46
- function getUrl(resource, apiVersion) {
50
+ function setUrl(resource, apiVersion) {
47
51
  if (isValidHttpUrl(resource))
48
52
  return resource;
49
53
  const pathSuffix = apiVersion ? `${apiVersion}/${resource}` : `v1.0/${resource}`;
@@ -58,9 +62,6 @@ function isValidHttpUrl(string) {
58
62
  }
59
63
  return url.protocol === "http:" || url.protocol === "https:";
60
64
  }
61
- function getAuth(token) {
62
- return token ? { headers: { Authorization: `Bearer ${token}` } } : null;
63
- }
64
65
  var isStream = (value) => {
65
66
  if (value && typeof value == "object") {
66
67
  if (value instanceof Readable || value instanceof Writable) {
@@ -101,21 +102,22 @@ function handleResponseError(response, data, method) {
101
102
  throw new Error(errorString);
102
103
  }
103
104
  }
104
- var request = async (urlString, params = {}, method = "GET") => {
105
- let url = urlString;
106
- const defaultHeaders = { "Content-Type": "application/json" };
107
- const { headers, parseAs } = params;
108
- const setHeaders = { ...headers, ...defaultHeaders };
109
- delete params.parseAs;
110
- delete params.headers;
111
- let options = {
112
- method,
113
- headers: setHeaders
114
- };
115
- if ("GET" === method) {
116
- url = `${urlString}?${new URLSearchParams(params).toString()}`;
117
- } else {
118
- options.body = JSON.stringify(params);
105
+ var defaultOptions = {
106
+ method: "GET",
107
+ headers: {
108
+ "Content-Type": "application/json"
109
+ },
110
+ accessToken: ""
111
+ };
112
+ var request = async (path, params) => {
113
+ let url = path;
114
+ const options = { ...defaultOptions, ...params };
115
+ const { parseAs, query, method, accessToken } = options;
116
+ delete options.parseAs;
117
+ delete options.accessToken;
118
+ options.headers["Authorization"] = makeAuthHeader(accessToken);
119
+ if (method === "GET" && query) {
120
+ url = `${path}?${new URLSearchParams(query).toString()}`;
119
121
  }
120
122
  const response = await fetch(url, options);
121
123
  const contentType = response.headers.get("Content-Type");
@@ -131,6 +133,41 @@ var request = async (urlString, params = {}, method = "GET") => {
131
133
  handleResponseError(response, data, method);
132
134
  return data;
133
135
  };
136
+ function makeAuthHeader(accessToken) {
137
+ return accessToken ? `Bearer ${accessToken}` : null;
138
+ }
139
+ var defaultSheetOptions = {
140
+ bookType: "xlsx",
141
+ wsName: "Sheet"
142
+ };
143
+ function sheetToBuffer(rows, options, callback) {
144
+ return (state) => {
145
+ const resolvedRows = asData(rows, state);
146
+ const [resolvedOptions] = expandReferences(state, options);
147
+ const { wsName, bookType } = {
148
+ ...defaultSheetOptions,
149
+ ...resolvedOptions
150
+ };
151
+ const workbook = xlsx.utils.book_new();
152
+ const worksheet = xlsx.utils.json_to_sheet(resolvedRows);
153
+ xlsx.utils.book_append_sheet(workbook, worksheet, wsName);
154
+ const buffer = xlsx.write(workbook, { type: "buffer", bookType });
155
+ console.log(`Creating sheet buffer with bookType '${bookType}'`);
156
+ const nextState = { ...state, buffer };
157
+ if (callback)
158
+ return callback(nextState);
159
+ return nextState;
160
+ };
161
+ }
162
+ function assertResources(resources) {
163
+ const { driveId, siteId, folderId } = resources;
164
+ if (driveId && siteId)
165
+ throw new Error('Use either "driveId" or "siteId" not both');
166
+ if (!driveId && !siteId)
167
+ throw new Error('"siteId" or "driveId" is required');
168
+ if (!folderId)
169
+ throw new Error("Parent Item Id is required");
170
+ }
134
171
 
135
172
  // src/Adaptor.js
136
173
  import {
@@ -153,8 +190,13 @@ function execute(...operations) {
153
190
  drives: {}
154
191
  };
155
192
  const cleanup = (finalState) => {
156
- const { drives, ...rest } = finalState;
157
- return rest;
193
+ if (finalState == null ? void 0 : finalState.buffer) {
194
+ delete finalState.buffer;
195
+ }
196
+ if (finalState == null ? void 0 : finalState.drives) {
197
+ delete finalState.drives;
198
+ }
199
+ return finalState;
158
200
  };
159
201
  return (state) => {
160
202
  return commonExecute(...operations)({
@@ -168,19 +210,19 @@ function execute(...operations) {
168
210
  }
169
211
  function create(resource, data, callback) {
170
212
  return (state) => {
171
- const [resolvedResource, resolvedData] = expandReferences(
213
+ const [resolvedResource, resolvedData] = expandReferences2(
172
214
  state,
173
215
  resource,
174
216
  data
175
217
  );
176
218
  const { accessToken, apiVersion } = state.configuration;
177
- const url = getUrl({ apiVersion, resolvedResource });
178
- const auth = getAuth(accessToken);
219
+ const url = setUrl({ apiVersion, resolvedResource });
179
220
  const options = {
180
- auth,
181
- ...resolvedData
221
+ accessToken,
222
+ body: JSON.stringify(resolvedData),
223
+ method: "POST"
182
224
  };
183
- return request(url, options, "POST").then(
225
+ return request(url, options).then(
184
226
  (response) => handleResponse(response, state, callback)
185
227
  );
186
228
  };
@@ -188,10 +230,9 @@ function create(resource, data, callback) {
188
230
  function get(path, query, callback = false) {
189
231
  return (state) => {
190
232
  const { accessToken, apiVersion } = state.configuration;
191
- const [resolvedPath, resolvedQuery] = expandReferences(state, path, query);
192
- const url = getUrl(resolvedPath, apiVersion);
193
- const auth = getAuth(accessToken);
194
- return request(url, { ...resolvedQuery, ...auth }).then(
233
+ const [resolvedPath, resolvedQuery] = expandReferences2(state, path, query);
234
+ const url = setUrl(resolvedPath, apiVersion);
235
+ return request(url, { query: resolvedQuery, accessToken }).then(
195
236
  (response) => handleResponse(response, state, callback)
196
237
  );
197
238
  };
@@ -199,7 +240,7 @@ function get(path, query, callback = false) {
199
240
  function getDrive(specifier, name = "default", callback = (s) => s) {
200
241
  return (state) => {
201
242
  const { accessToken, apiVersion } = state.configuration;
202
- const [resolvedSpecifier, resolvedName] = expandReferences(
243
+ const [resolvedSpecifier, resolvedName] = expandReferences2(
203
244
  state,
204
245
  specifier,
205
246
  name
@@ -211,9 +252,8 @@ function getDrive(specifier, name = "default", callback = (s) => s) {
211
252
  } else {
212
253
  resource = `${owner}/${id}/drive`;
213
254
  }
214
- const url = getUrl(resource, apiVersion);
215
- const auth = getAuth(accessToken);
216
- return request(url, { ...auth }).then((response) => {
255
+ const url = setUrl(resource, apiVersion);
256
+ return request(url, { accessToken }).then((response) => {
217
257
  state.drives[resolvedName] = response;
218
258
  return callback(state);
219
259
  });
@@ -221,17 +261,17 @@ function getDrive(specifier, name = "default", callback = (s) => s) {
221
261
  }
222
262
  function getFolder(pathOrId, options, callback = (s) => s) {
223
263
  return async (state) => {
224
- const defaultOptions = {
264
+ const defaultOptions2 = {
225
265
  driveName: "default",
226
266
  metadata: false
227
267
  };
228
268
  const { accessToken, apiVersion } = state.configuration;
229
- const [resolvedPathOrId, resolvedOptions] = expandReferences(
269
+ const [resolvedPathOrId, resolvedOptions] = expandReferences2(
230
270
  state,
231
271
  pathOrId,
232
272
  options
233
273
  );
234
- const { driveName, metadata } = { ...defaultOptions, ...resolvedOptions };
274
+ const { driveName, metadata } = { ...defaultOptions2, ...resolvedOptions };
235
275
  assertDrive(state, driveName);
236
276
  const { id: driveId } = state.drives[driveName];
237
277
  let resource;
@@ -245,27 +285,26 @@ function getFolder(pathOrId, options, callback = (s) => s) {
245
285
  if (!metadata) {
246
286
  resource += resolvedPathOrId.startsWith("/") ? ":/children" : "/children";
247
287
  }
248
- const url = getUrl(resource, apiVersion);
249
- const auth = getAuth(accessToken);
250
- return request(url, { ...auth }).then(
288
+ const url = setUrl(resource, apiVersion);
289
+ return request(url, { accessToken }).then(
251
290
  (response) => handleResponse(response, state, callback)
252
291
  );
253
292
  };
254
293
  }
255
294
  function getFile(pathOrId, options, callback = (s) => s) {
256
- const defaultOptions = {
295
+ const defaultOptions2 = {
257
296
  driveName: "default",
258
297
  metadata: false
259
298
  };
260
299
  return async (state) => {
261
300
  const { accessToken, apiVersion } = state.configuration;
262
- const [resolvedPathOrId, resolvedOptions] = expandReferences(
301
+ const [resolvedPathOrId, resolvedOptions] = expandReferences2(
263
302
  state,
264
303
  pathOrId,
265
304
  options
266
305
  );
267
306
  const { driveName, metadata } = {
268
- ...defaultOptions,
307
+ ...defaultOptions2,
269
308
  ...resolvedOptions
270
309
  };
271
310
  assertDrive(state, driveName);
@@ -281,15 +320,60 @@ function getFile(pathOrId, options, callback = (s) => s) {
281
320
  if (!metadata) {
282
321
  resource += resolvedPathOrId.startsWith("/") ? ":/content" : "/content";
283
322
  }
284
- const url = getUrl(resource, apiVersion);
285
- const auth = getAuth(accessToken);
323
+ const url = setUrl(resource, apiVersion);
286
324
  const response = await request(url, {
287
- ...auth,
325
+ accessToken,
288
326
  parseAs: metadata ? "json" : "text"
289
327
  });
290
328
  return handleResponse(response, state, callback);
291
329
  };
292
330
  }
331
+ var defaultResource = {
332
+ contentType: "application/octet-stream",
333
+ driveId: "",
334
+ folderId: "",
335
+ fileName: "sheet.xls",
336
+ onConflict: "replace"
337
+ };
338
+ function uploadFile(resource, data, callback) {
339
+ return async (state) => {
340
+ const { accessToken, apiVersion } = state.configuration;
341
+ const [resolvedResource, resolvedData] = expandReferences2(
342
+ state,
343
+ resource,
344
+ data
345
+ );
346
+ const { contentType, driveId, siteId, folderId, onConflict, fileName } = {
347
+ ...defaultResource,
348
+ ...resolvedResource
349
+ };
350
+ assertResources({ driveId, siteId, folderId });
351
+ const path = driveId && `drives/${driveId}/items/${folderId}:/${fileName}:/createUploadSession` || siteId && `sites/${siteId}/drive/items/${folderId}:/${fileName}:/createUploadSession`;
352
+ const uploadSession = await request(setUrl(path, apiVersion), {
353
+ method: "POST",
354
+ accessToken,
355
+ headers: {
356
+ "Content-Type": "application/json"
357
+ },
358
+ body: JSON.stringify({
359
+ "@microsoft.graph.conflictBehavior": onConflict,
360
+ name: fileName
361
+ })
362
+ });
363
+ const uploadUrl = uploadSession.uploadUrl;
364
+ console.log(`Uploading file...`);
365
+ return request(uploadUrl, {
366
+ method: "PUT",
367
+ accessToken,
368
+ headers: {
369
+ "Content-Type": contentType,
370
+ "Content-Length": `${resolvedData.length}`,
371
+ "Content-Range": `bytes 0-${resolvedData.length - 1}/${resolvedData.length}`
372
+ },
373
+ body: resolvedData
374
+ }).then((response) => handleResponse(response, state, callback));
375
+ };
376
+ }
293
377
 
294
378
  // src/index.js
295
379
  var src_default = Adaptor_exports;
@@ -312,5 +396,7 @@ export {
312
396
  merge,
313
397
  parseCsv,
314
398
  request,
315
- sourceValue
399
+ sheetToBuffer,
400
+ sourceValue,
401
+ uploadFile
316
402
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfn/language-msgraph",
3
- "version": "0.3.4",
3
+ "version": "0.4.0",
4
4
  "description": "Microsoft Graph Language Pack for OpenFn",
5
5
  "type": "module",
6
6
  "exports": {
@@ -20,7 +20,8 @@
20
20
  ],
21
21
  "dependencies": {
22
22
  "undici": "^5.22.1",
23
- "@openfn/language-common": "1.10.3"
23
+ "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.0/xlsx-0.20.0.tgz",
24
+ "@openfn/language-common": "1.11.1"
24
25
  },
25
26
  "devDependencies": {
26
27
  "@openfn/buildtools": "^1.0.2",
@@ -79,5 +79,46 @@ export function getFolder(pathOrId: string, options: object, callback?: Function
79
79
  * @return {Operation}
80
80
  */
81
81
  export function getFile(pathOrId: string, options: object, callback?: Function): Operation;
82
- export { request } from "./Utils";
82
+ /**
83
+ * Upload a file to a drive
84
+ * @public
85
+ * @example
86
+ * <caption>Upload Excel file to a drive using `driveId` and `parantItemId`</caption>
87
+ * uploadFile(
88
+ * state => ({
89
+ * driveId: state.driveId,
90
+ * folderId: state.folderId,
91
+ * fileName: `Tracker.xlsx`,
92
+ * }),
93
+ * state => state.buffer
94
+ * );
95
+ * @example
96
+ * <caption>Upload Excel file to a SharePoint drive using `siteId` and `parantItemId`</caption>
97
+ * uploadFile(
98
+ * state => ({
99
+ * siteId: state.siteId,
100
+ * folderId: state.folderId,
101
+ * fileName: `Report.xlsx`,
102
+ * }),
103
+ * state => state.buffer
104
+ * );
105
+ * @function
106
+ * @param {Object} resource - Resource Object
107
+ * @param {String} [resource.driveId] - Drive Id
108
+ * @param {String} [resource.driveId] - Site Id
109
+ * @param {String} [resource.folderId] - Parent folder id
110
+ * @param {String} [resource.contentType] - Resource content-type
111
+ * @param {String} [resource.onConflict] - Specify conflict behavior if file with the same name exists. Can be "rename | fail | replace"
112
+ * @param {Object} data - A buffer containing the file.
113
+ * @param {Function} callback - Optional callback function
114
+ * @returns {Operation}
115
+ */
116
+ export function uploadFile(resource: {
117
+ driveId?: string;
118
+ driveId?: string;
119
+ folderId?: string;
120
+ contentType?: string;
121
+ onConflict?: string;
122
+ }, data: any, callback: Function): Operation;
123
+ export { request, sheetToBuffer } from "./Utils";
83
124
  export { dataPath, dataValue, dateFns, each, field, fields, fn, lastReferenceValue, merge, sourceValue, parseCsv } from "@openfn/language-common";
package/types/Utils.d.ts CHANGED
@@ -1,10 +1,27 @@
1
1
  export function assertDrive(state: any, driveName: any): void;
2
- export function getUrl(resource: any, apiVersion: any): any;
3
- export function getAuth(token: any): {
4
- headers: {
5
- Authorization: string;
6
- };
7
- };
2
+ export function setUrl(resource: any, apiVersion: any): any;
8
3
  export function handleResponse(response: any, state: any, callback: any): any;
9
4
  export function handleResponseError(response: any, data: any, method: any): void;
10
- export function request(urlString: any, params?: object, method?: string): Promise<unknown>;
5
+ /**
6
+ * The function `sheetToBuffer` takes in rows, options and optional callback, It creates a workbook
7
+ * and worksheet using the rows, appends the worksheet to the workbook, and returns the workbook as a
8
+ * buffer.
9
+ * @public
10
+ * @example
11
+ * <caption>Create a buffer containing excel file with `xlsx` output format </caption>
12
+ * sheetToBuffer('$.data[*]', {
13
+ * wsName: 'Invalid Grant Codes',
14
+ * bookType: 'xlsx',
15
+ * });
16
+ * @param rows - The `rows` parameter is an array of objects representing the data to be written to the
17
+ * Excel sheet. Each object in the array represents a row in the sheet, and the keys of the object
18
+ * represent the column headers. The values of the object represent the data in each cell of the row.
19
+ * @param options - The `options` parameter is an object that contains additional configuration options
20
+ * @param {String} [options.wsName] - Worksheet name i.e 32 Characters
21
+ * @param {String} [options.bookType] - File format of the exported file, Default is 'xlsx'. See {@link https://docs.sheetjs.com/docs/api/write-options/#supported-output-formats here}
22
+ * for the function. It can have the following properties:
23
+ * @returns a buffer containing the Excel file in `state.buffer`.
24
+ */
25
+ export function sheetToBuffer(rows: any, options: any, callback: any): (state: any) => any;
26
+ export function assertResources(resources: any): void;
27
+ export function request(path: any, params: any): Promise<unknown>;