@openfn/language-msgraph 0.3.5 → 0.5.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": [],
@@ -820,6 +949,83 @@
820
949
  ]
821
950
  },
822
951
  "valid": true
952
+ },
953
+ {
954
+ "name": "cursor",
955
+ "params": [
956
+ "value",
957
+ "options"
958
+ ],
959
+ "docs": {
960
+ "description": "Sets a cursor property on state.\nSupports natural language dates like `now`, `today`, `yesterday`, `n hours ago`, `n days ago`, and `start`,\nwhich will be converted relative to the environment (ie, the Lightning or CLI locale). Custom timezones \nare not yet supported.\nSee the usage guide at @{link https://docs.openfn.org/documentation/jobs/job-writing-guide#using-cursors}",
961
+ "tags": [
962
+ {
963
+ "title": "public",
964
+ "description": null,
965
+ "type": null
966
+ },
967
+ {
968
+ "title": "example",
969
+ "description": "cursor($.cursor, { defaultValue: 'today' })",
970
+ "caption": "Use a cursor from state if present, or else use the default value"
971
+ },
972
+ {
973
+ "title": "example",
974
+ "description": "cursor(22)",
975
+ "caption": "Use a pagination cursor"
976
+ },
977
+ {
978
+ "title": "function",
979
+ "description": null,
980
+ "name": null
981
+ },
982
+ {
983
+ "title": "param",
984
+ "description": "the cursor value. Usually an ISO date, natural language date, or page number",
985
+ "type": {
986
+ "type": "NameExpression",
987
+ "name": "any"
988
+ },
989
+ "name": "value"
990
+ },
991
+ {
992
+ "title": "param",
993
+ "description": "options to control the cursor.",
994
+ "type": {
995
+ "type": "NameExpression",
996
+ "name": "object"
997
+ },
998
+ "name": "options"
999
+ },
1000
+ {
1001
+ "title": "param",
1002
+ "description": "set the cursor key. Will persist through the whole run.",
1003
+ "type": {
1004
+ "type": "NameExpression",
1005
+ "name": "string"
1006
+ },
1007
+ "name": "options.key"
1008
+ },
1009
+ {
1010
+ "title": "param",
1011
+ "description": "the value to use if value is falsy",
1012
+ "type": {
1013
+ "type": "NameExpression",
1014
+ "name": "any"
1015
+ },
1016
+ "name": "options.defaultValue"
1017
+ },
1018
+ {
1019
+ "title": "returns",
1020
+ "description": null,
1021
+ "type": {
1022
+ "type": "NameExpression",
1023
+ "name": "Operation"
1024
+ }
1025
+ }
1026
+ ]
1027
+ },
1028
+ "valid": false
823
1029
  }
824
1030
  ]
825
1031
  }
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,12 +16,17 @@ 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
20
26
  var src_exports = {};
21
27
  __export(src_exports, {
22
28
  create: () => create,
29
+ cursor: () => import_language_common3.cursor,
23
30
  dataPath: () => import_language_common3.dataPath,
24
31
  dataValue: () => import_language_common3.dataValue,
25
32
  dateFns: () => import_language_common3.dateFns,
@@ -37,7 +44,9 @@ __export(src_exports, {
37
44
  merge: () => import_language_common3.merge,
38
45
  parseCsv: () => import_language_common3.parseCsv,
39
46
  request: () => request,
40
- sourceValue: () => import_language_common3.sourceValue
47
+ sheetToBuffer: () => sheetToBuffer,
48
+ sourceValue: () => import_language_common3.sourceValue,
49
+ uploadFile: () => uploadFile
41
50
  });
42
51
  module.exports = __toCommonJS(src_exports);
43
52
 
@@ -45,6 +54,7 @@ module.exports = __toCommonJS(src_exports);
45
54
  var Adaptor_exports = {};
46
55
  __export(Adaptor_exports, {
47
56
  create: () => create,
57
+ cursor: () => import_language_common3.cursor,
48
58
  dataPath: () => import_language_common3.dataPath,
49
59
  dataValue: () => import_language_common3.dataValue,
50
60
  dateFns: () => import_language_common3.dateFns,
@@ -61,15 +71,19 @@ __export(Adaptor_exports, {
61
71
  merge: () => import_language_common3.merge,
62
72
  parseCsv: () => import_language_common3.parseCsv,
63
73
  request: () => request,
64
- sourceValue: () => import_language_common3.sourceValue
74
+ sheetToBuffer: () => sheetToBuffer,
75
+ sourceValue: () => import_language_common3.sourceValue,
76
+ uploadFile: () => uploadFile
65
77
  });
66
78
  var import_language_common2 = require("@openfn/language-common");
67
- var import_util = require("@openfn/language-common/util");
79
+ var import_util2 = require("@openfn/language-common/util");
68
80
 
69
81
  // src/Utils.js
82
+ var import_xlsx = __toESM(require("xlsx"), 1);
70
83
  var import_undici = require("undici");
71
84
  var import_node_stream = require("stream");
72
85
  var import_language_common = require("@openfn/language-common");
86
+ var import_util = require("@openfn/language-common/util");
73
87
  function assertDrive(state, driveName) {
74
88
  if (!state.drives[driveName]) {
75
89
  const errorString = [
@@ -80,7 +94,7 @@ function assertDrive(state, driveName) {
80
94
  throw new Error(errorString);
81
95
  }
82
96
  }
83
- function getUrl(resource, apiVersion) {
97
+ function setUrl(resource, apiVersion) {
84
98
  if (isValidHttpUrl(resource))
85
99
  return resource;
86
100
  const pathSuffix = apiVersion ? `${apiVersion}/${resource}` : `v1.0/${resource}`;
@@ -95,9 +109,6 @@ function isValidHttpUrl(string) {
95
109
  }
96
110
  return url.protocol === "http:" || url.protocol === "https:";
97
111
  }
98
- function getAuth(token) {
99
- return token ? { headers: { Authorization: `Bearer ${token}` } } : null;
100
- }
101
112
  var isStream = (value) => {
102
113
  if (value && typeof value == "object") {
103
114
  if (value instanceof import_node_stream.Readable || value instanceof import_node_stream.Writable) {
@@ -138,21 +149,22 @@ function handleResponseError(response, data, method) {
138
149
  throw new Error(errorString);
139
150
  }
140
151
  }
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);
152
+ var defaultOptions = {
153
+ method: "GET",
154
+ headers: {
155
+ "Content-Type": "application/json"
156
+ },
157
+ accessToken: ""
158
+ };
159
+ var request = async (path, params) => {
160
+ let url = path;
161
+ const options = { ...defaultOptions, ...params };
162
+ const { parseAs, query, method, accessToken } = options;
163
+ delete options.parseAs;
164
+ delete options.accessToken;
165
+ options.headers["Authorization"] = makeAuthHeader(accessToken);
166
+ if (method === "GET" && query) {
167
+ url = `${path}?${new URLSearchParams(query).toString()}`;
156
168
  }
157
169
  const response = await (0, import_undici.fetch)(url, options);
158
170
  const contentType = response.headers.get("Content-Type");
@@ -168,6 +180,41 @@ var request = async (urlString, params = {}, method = "GET") => {
168
180
  handleResponseError(response, data, method);
169
181
  return data;
170
182
  };
183
+ function makeAuthHeader(accessToken) {
184
+ return accessToken ? `Bearer ${accessToken}` : null;
185
+ }
186
+ var defaultSheetOptions = {
187
+ bookType: "xlsx",
188
+ wsName: "Sheet"
189
+ };
190
+ function sheetToBuffer(rows, options, callback) {
191
+ return (state) => {
192
+ const resolvedRows = (0, import_language_common.asData)(rows, state);
193
+ const [resolvedOptions] = (0, import_util.expandReferences)(state, options);
194
+ const { wsName, bookType } = {
195
+ ...defaultSheetOptions,
196
+ ...resolvedOptions
197
+ };
198
+ const workbook = import_xlsx.default.utils.book_new();
199
+ const worksheet = import_xlsx.default.utils.json_to_sheet(resolvedRows);
200
+ import_xlsx.default.utils.book_append_sheet(workbook, worksheet, wsName);
201
+ const buffer = import_xlsx.default.write(workbook, { type: "buffer", bookType });
202
+ console.log(`Creating sheet buffer with bookType '${bookType}'`);
203
+ const nextState = { ...state, buffer };
204
+ if (callback)
205
+ return callback(nextState);
206
+ return nextState;
207
+ };
208
+ }
209
+ function assertResources(resources) {
210
+ const { driveId, siteId, folderId } = resources;
211
+ if (driveId && siteId)
212
+ throw new Error('Use either "driveId" or "siteId" not both');
213
+ if (!driveId && !siteId)
214
+ throw new Error('"siteId" or "driveId" is required');
215
+ if (!folderId)
216
+ throw new Error("Parent Item Id is required");
217
+ }
171
218
 
172
219
  // src/Adaptor.js
173
220
  var import_language_common3 = require("@openfn/language-common");
@@ -178,8 +225,13 @@ function execute(...operations) {
178
225
  drives: {}
179
226
  };
180
227
  const cleanup = (finalState) => {
181
- const { drives, ...rest } = finalState;
182
- return rest;
228
+ if (finalState == null ? void 0 : finalState.buffer) {
229
+ delete finalState.buffer;
230
+ }
231
+ if (finalState == null ? void 0 : finalState.drives) {
232
+ delete finalState.drives;
233
+ }
234
+ return finalState;
183
235
  };
184
236
  return (state) => {
185
237
  return (0, import_language_common2.execute)(...operations)({
@@ -193,19 +245,19 @@ function execute(...operations) {
193
245
  }
194
246
  function create(resource, data, callback) {
195
247
  return (state) => {
196
- const [resolvedResource, resolvedData] = (0, import_util.expandReferences)(
248
+ const [resolvedResource, resolvedData] = (0, import_util2.expandReferences)(
197
249
  state,
198
250
  resource,
199
251
  data
200
252
  );
201
253
  const { accessToken, apiVersion } = state.configuration;
202
- const url = getUrl({ apiVersion, resolvedResource });
203
- const auth = getAuth(accessToken);
254
+ const url = setUrl({ apiVersion, resolvedResource });
204
255
  const options = {
205
- auth,
206
- ...resolvedData
256
+ accessToken,
257
+ body: JSON.stringify(resolvedData),
258
+ method: "POST"
207
259
  };
208
- return request(url, options, "POST").then(
260
+ return request(url, options).then(
209
261
  (response) => handleResponse(response, state, callback)
210
262
  );
211
263
  };
@@ -213,10 +265,9 @@ function create(resource, data, callback) {
213
265
  function get(path, query, callback = false) {
214
266
  return (state) => {
215
267
  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(
268
+ const [resolvedPath, resolvedQuery] = (0, import_util2.expandReferences)(state, path, query);
269
+ const url = setUrl(resolvedPath, apiVersion);
270
+ return request(url, { query: resolvedQuery, accessToken }).then(
220
271
  (response) => handleResponse(response, state, callback)
221
272
  );
222
273
  };
@@ -224,7 +275,7 @@ function get(path, query, callback = false) {
224
275
  function getDrive(specifier, name = "default", callback = (s) => s) {
225
276
  return (state) => {
226
277
  const { accessToken, apiVersion } = state.configuration;
227
- const [resolvedSpecifier, resolvedName] = (0, import_util.expandReferences)(
278
+ const [resolvedSpecifier, resolvedName] = (0, import_util2.expandReferences)(
228
279
  state,
229
280
  specifier,
230
281
  name
@@ -236,9 +287,8 @@ function getDrive(specifier, name = "default", callback = (s) => s) {
236
287
  } else {
237
288
  resource = `${owner}/${id}/drive`;
238
289
  }
239
- const url = getUrl(resource, apiVersion);
240
- const auth = getAuth(accessToken);
241
- return request(url, { ...auth }).then((response) => {
290
+ const url = setUrl(resource, apiVersion);
291
+ return request(url, { accessToken }).then((response) => {
242
292
  state.drives[resolvedName] = response;
243
293
  return callback(state);
244
294
  });
@@ -246,17 +296,17 @@ function getDrive(specifier, name = "default", callback = (s) => s) {
246
296
  }
247
297
  function getFolder(pathOrId, options, callback = (s) => s) {
248
298
  return async (state) => {
249
- const defaultOptions = {
299
+ const defaultOptions2 = {
250
300
  driveName: "default",
251
301
  metadata: false
252
302
  };
253
303
  const { accessToken, apiVersion } = state.configuration;
254
- const [resolvedPathOrId, resolvedOptions] = (0, import_util.expandReferences)(
304
+ const [resolvedPathOrId, resolvedOptions] = (0, import_util2.expandReferences)(
255
305
  state,
256
306
  pathOrId,
257
307
  options
258
308
  );
259
- const { driveName, metadata } = { ...defaultOptions, ...resolvedOptions };
309
+ const { driveName, metadata } = { ...defaultOptions2, ...resolvedOptions };
260
310
  assertDrive(state, driveName);
261
311
  const { id: driveId } = state.drives[driveName];
262
312
  let resource;
@@ -270,27 +320,26 @@ function getFolder(pathOrId, options, callback = (s) => s) {
270
320
  if (!metadata) {
271
321
  resource += resolvedPathOrId.startsWith("/") ? ":/children" : "/children";
272
322
  }
273
- const url = getUrl(resource, apiVersion);
274
- const auth = getAuth(accessToken);
275
- return request(url, { ...auth }).then(
323
+ const url = setUrl(resource, apiVersion);
324
+ return request(url, { accessToken }).then(
276
325
  (response) => handleResponse(response, state, callback)
277
326
  );
278
327
  };
279
328
  }
280
329
  function getFile(pathOrId, options, callback = (s) => s) {
281
- const defaultOptions = {
330
+ const defaultOptions2 = {
282
331
  driveName: "default",
283
332
  metadata: false
284
333
  };
285
334
  return async (state) => {
286
335
  const { accessToken, apiVersion } = state.configuration;
287
- const [resolvedPathOrId, resolvedOptions] = (0, import_util.expandReferences)(
336
+ const [resolvedPathOrId, resolvedOptions] = (0, import_util2.expandReferences)(
288
337
  state,
289
338
  pathOrId,
290
339
  options
291
340
  );
292
341
  const { driveName, metadata } = {
293
- ...defaultOptions,
342
+ ...defaultOptions2,
294
343
  ...resolvedOptions
295
344
  };
296
345
  assertDrive(state, driveName);
@@ -306,21 +355,67 @@ function getFile(pathOrId, options, callback = (s) => s) {
306
355
  if (!metadata) {
307
356
  resource += resolvedPathOrId.startsWith("/") ? ":/content" : "/content";
308
357
  }
309
- const url = getUrl(resource, apiVersion);
310
- const auth = getAuth(accessToken);
358
+ const url = setUrl(resource, apiVersion);
311
359
  const response = await request(url, {
312
- ...auth,
360
+ accessToken,
313
361
  parseAs: metadata ? "json" : "text"
314
362
  });
315
363
  return handleResponse(response, state, callback);
316
364
  };
317
365
  }
366
+ var defaultResource = {
367
+ contentType: "application/octet-stream",
368
+ driveId: "",
369
+ folderId: "",
370
+ fileName: "sheet.xls",
371
+ onConflict: "replace"
372
+ };
373
+ function uploadFile(resource, data, callback) {
374
+ return async (state) => {
375
+ const { accessToken, apiVersion } = state.configuration;
376
+ const [resolvedResource, resolvedData] = (0, import_util2.expandReferences)(
377
+ state,
378
+ resource,
379
+ data
380
+ );
381
+ const { contentType, driveId, siteId, folderId, onConflict, fileName } = {
382
+ ...defaultResource,
383
+ ...resolvedResource
384
+ };
385
+ assertResources({ driveId, siteId, folderId });
386
+ const path = driveId && `drives/${driveId}/items/${folderId}:/${fileName}:/createUploadSession` || siteId && `sites/${siteId}/drive/items/${folderId}:/${fileName}:/createUploadSession`;
387
+ const uploadSession = await request(setUrl(path, apiVersion), {
388
+ method: "POST",
389
+ accessToken,
390
+ headers: {
391
+ "Content-Type": "application/json"
392
+ },
393
+ body: JSON.stringify({
394
+ "@microsoft.graph.conflictBehavior": onConflict,
395
+ name: fileName
396
+ })
397
+ });
398
+ const uploadUrl = uploadSession.uploadUrl;
399
+ console.log(`Uploading file...`);
400
+ return request(uploadUrl, {
401
+ method: "PUT",
402
+ accessToken,
403
+ headers: {
404
+ "Content-Type": contentType,
405
+ "Content-Length": `${resolvedData.length}`,
406
+ "Content-Range": `bytes 0-${resolvedData.length - 1}/${resolvedData.length}`
407
+ },
408
+ body: resolvedData
409
+ }).then((response) => handleResponse(response, state, callback));
410
+ };
411
+ }
318
412
 
319
413
  // src/index.js
320
414
  var src_default = Adaptor_exports;
321
415
  // Annotate the CommonJS export names for ESM import in node:
322
416
  0 && (module.exports = {
323
417
  create,
418
+ cursor,
324
419
  dataPath,
325
420
  dataValue,
326
421
  dateFns,
@@ -337,5 +432,7 @@ var src_default = Adaptor_exports;
337
432
  merge,
338
433
  parseCsv,
339
434
  request,
340
- sourceValue
435
+ sheetToBuffer,
436
+ sourceValue,
437
+ uploadFile
341
438
  });
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ var __export = (target, all) => {
8
8
  var Adaptor_exports = {};
9
9
  __export(Adaptor_exports, {
10
10
  create: () => create,
11
+ cursor: () => cursor,
11
12
  dataPath: () => dataPath,
12
13
  dataValue: () => dataValue,
13
14
  dateFns: () => dateFns,
@@ -24,15 +25,19 @@ __export(Adaptor_exports, {
24
25
  merge: () => merge,
25
26
  parseCsv: () => parseCsv,
26
27
  request: () => request,
27
- sourceValue: () => sourceValue
28
+ sheetToBuffer: () => sheetToBuffer,
29
+ sourceValue: () => sourceValue,
30
+ uploadFile: () => uploadFile
28
31
  });
29
32
  import { execute as commonExecute } from "@openfn/language-common";
30
- import { expandReferences } from "@openfn/language-common/util";
33
+ import { expandReferences as expandReferences2 } from "@openfn/language-common/util";
31
34
 
32
35
  // src/Utils.js
36
+ import xlsx from "xlsx";
33
37
  import { fetch } from "undici";
34
38
  import { Readable, Writable } from "stream";
35
- import { composeNextState } from "@openfn/language-common";
39
+ import { composeNextState, asData } from "@openfn/language-common";
40
+ import { expandReferences } from "@openfn/language-common/util";
36
41
  function assertDrive(state, driveName) {
37
42
  if (!state.drives[driveName]) {
38
43
  const errorString = [
@@ -43,7 +48,7 @@ function assertDrive(state, driveName) {
43
48
  throw new Error(errorString);
44
49
  }
45
50
  }
46
- function getUrl(resource, apiVersion) {
51
+ function setUrl(resource, apiVersion) {
47
52
  if (isValidHttpUrl(resource))
48
53
  return resource;
49
54
  const pathSuffix = apiVersion ? `${apiVersion}/${resource}` : `v1.0/${resource}`;
@@ -58,9 +63,6 @@ function isValidHttpUrl(string) {
58
63
  }
59
64
  return url.protocol === "http:" || url.protocol === "https:";
60
65
  }
61
- function getAuth(token) {
62
- return token ? { headers: { Authorization: `Bearer ${token}` } } : null;
63
- }
64
66
  var isStream = (value) => {
65
67
  if (value && typeof value == "object") {
66
68
  if (value instanceof Readable || value instanceof Writable) {
@@ -101,21 +103,22 @@ function handleResponseError(response, data, method) {
101
103
  throw new Error(errorString);
102
104
  }
103
105
  }
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);
106
+ var defaultOptions = {
107
+ method: "GET",
108
+ headers: {
109
+ "Content-Type": "application/json"
110
+ },
111
+ accessToken: ""
112
+ };
113
+ var request = async (path, params) => {
114
+ let url = path;
115
+ const options = { ...defaultOptions, ...params };
116
+ const { parseAs, query, method, accessToken } = options;
117
+ delete options.parseAs;
118
+ delete options.accessToken;
119
+ options.headers["Authorization"] = makeAuthHeader(accessToken);
120
+ if (method === "GET" && query) {
121
+ url = `${path}?${new URLSearchParams(query).toString()}`;
119
122
  }
120
123
  const response = await fetch(url, options);
121
124
  const contentType = response.headers.get("Content-Type");
@@ -131,9 +134,45 @@ var request = async (urlString, params = {}, method = "GET") => {
131
134
  handleResponseError(response, data, method);
132
135
  return data;
133
136
  };
137
+ function makeAuthHeader(accessToken) {
138
+ return accessToken ? `Bearer ${accessToken}` : null;
139
+ }
140
+ var defaultSheetOptions = {
141
+ bookType: "xlsx",
142
+ wsName: "Sheet"
143
+ };
144
+ function sheetToBuffer(rows, options, callback) {
145
+ return (state) => {
146
+ const resolvedRows = asData(rows, state);
147
+ const [resolvedOptions] = expandReferences(state, options);
148
+ const { wsName, bookType } = {
149
+ ...defaultSheetOptions,
150
+ ...resolvedOptions
151
+ };
152
+ const workbook = xlsx.utils.book_new();
153
+ const worksheet = xlsx.utils.json_to_sheet(resolvedRows);
154
+ xlsx.utils.book_append_sheet(workbook, worksheet, wsName);
155
+ const buffer = xlsx.write(workbook, { type: "buffer", bookType });
156
+ console.log(`Creating sheet buffer with bookType '${bookType}'`);
157
+ const nextState = { ...state, buffer };
158
+ if (callback)
159
+ return callback(nextState);
160
+ return nextState;
161
+ };
162
+ }
163
+ function assertResources(resources) {
164
+ const { driveId, siteId, folderId } = resources;
165
+ if (driveId && siteId)
166
+ throw new Error('Use either "driveId" or "siteId" not both');
167
+ if (!driveId && !siteId)
168
+ throw new Error('"siteId" or "driveId" is required');
169
+ if (!folderId)
170
+ throw new Error("Parent Item Id is required");
171
+ }
134
172
 
135
173
  // src/Adaptor.js
136
174
  import {
175
+ cursor,
137
176
  dataPath,
138
177
  dataValue,
139
178
  dateFns,
@@ -153,8 +192,13 @@ function execute(...operations) {
153
192
  drives: {}
154
193
  };
155
194
  const cleanup = (finalState) => {
156
- const { drives, ...rest } = finalState;
157
- return rest;
195
+ if (finalState == null ? void 0 : finalState.buffer) {
196
+ delete finalState.buffer;
197
+ }
198
+ if (finalState == null ? void 0 : finalState.drives) {
199
+ delete finalState.drives;
200
+ }
201
+ return finalState;
158
202
  };
159
203
  return (state) => {
160
204
  return commonExecute(...operations)({
@@ -168,19 +212,19 @@ function execute(...operations) {
168
212
  }
169
213
  function create(resource, data, callback) {
170
214
  return (state) => {
171
- const [resolvedResource, resolvedData] = expandReferences(
215
+ const [resolvedResource, resolvedData] = expandReferences2(
172
216
  state,
173
217
  resource,
174
218
  data
175
219
  );
176
220
  const { accessToken, apiVersion } = state.configuration;
177
- const url = getUrl({ apiVersion, resolvedResource });
178
- const auth = getAuth(accessToken);
221
+ const url = setUrl({ apiVersion, resolvedResource });
179
222
  const options = {
180
- auth,
181
- ...resolvedData
223
+ accessToken,
224
+ body: JSON.stringify(resolvedData),
225
+ method: "POST"
182
226
  };
183
- return request(url, options, "POST").then(
227
+ return request(url, options).then(
184
228
  (response) => handleResponse(response, state, callback)
185
229
  );
186
230
  };
@@ -188,10 +232,9 @@ function create(resource, data, callback) {
188
232
  function get(path, query, callback = false) {
189
233
  return (state) => {
190
234
  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(
235
+ const [resolvedPath, resolvedQuery] = expandReferences2(state, path, query);
236
+ const url = setUrl(resolvedPath, apiVersion);
237
+ return request(url, { query: resolvedQuery, accessToken }).then(
195
238
  (response) => handleResponse(response, state, callback)
196
239
  );
197
240
  };
@@ -199,7 +242,7 @@ function get(path, query, callback = false) {
199
242
  function getDrive(specifier, name = "default", callback = (s) => s) {
200
243
  return (state) => {
201
244
  const { accessToken, apiVersion } = state.configuration;
202
- const [resolvedSpecifier, resolvedName] = expandReferences(
245
+ const [resolvedSpecifier, resolvedName] = expandReferences2(
203
246
  state,
204
247
  specifier,
205
248
  name
@@ -211,9 +254,8 @@ function getDrive(specifier, name = "default", callback = (s) => s) {
211
254
  } else {
212
255
  resource = `${owner}/${id}/drive`;
213
256
  }
214
- const url = getUrl(resource, apiVersion);
215
- const auth = getAuth(accessToken);
216
- return request(url, { ...auth }).then((response) => {
257
+ const url = setUrl(resource, apiVersion);
258
+ return request(url, { accessToken }).then((response) => {
217
259
  state.drives[resolvedName] = response;
218
260
  return callback(state);
219
261
  });
@@ -221,17 +263,17 @@ function getDrive(specifier, name = "default", callback = (s) => s) {
221
263
  }
222
264
  function getFolder(pathOrId, options, callback = (s) => s) {
223
265
  return async (state) => {
224
- const defaultOptions = {
266
+ const defaultOptions2 = {
225
267
  driveName: "default",
226
268
  metadata: false
227
269
  };
228
270
  const { accessToken, apiVersion } = state.configuration;
229
- const [resolvedPathOrId, resolvedOptions] = expandReferences(
271
+ const [resolvedPathOrId, resolvedOptions] = expandReferences2(
230
272
  state,
231
273
  pathOrId,
232
274
  options
233
275
  );
234
- const { driveName, metadata } = { ...defaultOptions, ...resolvedOptions };
276
+ const { driveName, metadata } = { ...defaultOptions2, ...resolvedOptions };
235
277
  assertDrive(state, driveName);
236
278
  const { id: driveId } = state.drives[driveName];
237
279
  let resource;
@@ -245,27 +287,26 @@ function getFolder(pathOrId, options, callback = (s) => s) {
245
287
  if (!metadata) {
246
288
  resource += resolvedPathOrId.startsWith("/") ? ":/children" : "/children";
247
289
  }
248
- const url = getUrl(resource, apiVersion);
249
- const auth = getAuth(accessToken);
250
- return request(url, { ...auth }).then(
290
+ const url = setUrl(resource, apiVersion);
291
+ return request(url, { accessToken }).then(
251
292
  (response) => handleResponse(response, state, callback)
252
293
  );
253
294
  };
254
295
  }
255
296
  function getFile(pathOrId, options, callback = (s) => s) {
256
- const defaultOptions = {
297
+ const defaultOptions2 = {
257
298
  driveName: "default",
258
299
  metadata: false
259
300
  };
260
301
  return async (state) => {
261
302
  const { accessToken, apiVersion } = state.configuration;
262
- const [resolvedPathOrId, resolvedOptions] = expandReferences(
303
+ const [resolvedPathOrId, resolvedOptions] = expandReferences2(
263
304
  state,
264
305
  pathOrId,
265
306
  options
266
307
  );
267
308
  const { driveName, metadata } = {
268
- ...defaultOptions,
309
+ ...defaultOptions2,
269
310
  ...resolvedOptions
270
311
  };
271
312
  assertDrive(state, driveName);
@@ -281,20 +322,66 @@ function getFile(pathOrId, options, callback = (s) => s) {
281
322
  if (!metadata) {
282
323
  resource += resolvedPathOrId.startsWith("/") ? ":/content" : "/content";
283
324
  }
284
- const url = getUrl(resource, apiVersion);
285
- const auth = getAuth(accessToken);
325
+ const url = setUrl(resource, apiVersion);
286
326
  const response = await request(url, {
287
- ...auth,
327
+ accessToken,
288
328
  parseAs: metadata ? "json" : "text"
289
329
  });
290
330
  return handleResponse(response, state, callback);
291
331
  };
292
332
  }
333
+ var defaultResource = {
334
+ contentType: "application/octet-stream",
335
+ driveId: "",
336
+ folderId: "",
337
+ fileName: "sheet.xls",
338
+ onConflict: "replace"
339
+ };
340
+ function uploadFile(resource, data, callback) {
341
+ return async (state) => {
342
+ const { accessToken, apiVersion } = state.configuration;
343
+ const [resolvedResource, resolvedData] = expandReferences2(
344
+ state,
345
+ resource,
346
+ data
347
+ );
348
+ const { contentType, driveId, siteId, folderId, onConflict, fileName } = {
349
+ ...defaultResource,
350
+ ...resolvedResource
351
+ };
352
+ assertResources({ driveId, siteId, folderId });
353
+ const path = driveId && `drives/${driveId}/items/${folderId}:/${fileName}:/createUploadSession` || siteId && `sites/${siteId}/drive/items/${folderId}:/${fileName}:/createUploadSession`;
354
+ const uploadSession = await request(setUrl(path, apiVersion), {
355
+ method: "POST",
356
+ accessToken,
357
+ headers: {
358
+ "Content-Type": "application/json"
359
+ },
360
+ body: JSON.stringify({
361
+ "@microsoft.graph.conflictBehavior": onConflict,
362
+ name: fileName
363
+ })
364
+ });
365
+ const uploadUrl = uploadSession.uploadUrl;
366
+ console.log(`Uploading file...`);
367
+ return request(uploadUrl, {
368
+ method: "PUT",
369
+ accessToken,
370
+ headers: {
371
+ "Content-Type": contentType,
372
+ "Content-Length": `${resolvedData.length}`,
373
+ "Content-Range": `bytes 0-${resolvedData.length - 1}/${resolvedData.length}`
374
+ },
375
+ body: resolvedData
376
+ }).then((response) => handleResponse(response, state, callback));
377
+ };
378
+ }
293
379
 
294
380
  // src/index.js
295
381
  var src_default = Adaptor_exports;
296
382
  export {
297
383
  create,
384
+ cursor,
298
385
  dataPath,
299
386
  dataValue,
300
387
  dateFns,
@@ -312,5 +399,7 @@ export {
312
399
  merge,
313
400
  parseCsv,
314
401
  request,
315
- sourceValue
402
+ sheetToBuffer,
403
+ sourceValue,
404
+ uploadFile
316
405
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfn/language-msgraph",
3
- "version": "0.3.5",
3
+ "version": "0.5.0",
4
4
  "description": "Microsoft Graph Language Pack for OpenFn",
5
5
  "type": "module",
6
6
  "exports": {
@@ -20,10 +20,10 @@
20
20
  ],
21
21
  "dependencies": {
22
22
  "undici": "^5.22.1",
23
- "@openfn/language-common": "1.11.0"
23
+ "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.0/xlsx-0.20.0.tgz",
24
+ "@openfn/language-common": "1.13.0"
24
25
  },
25
26
  "devDependencies": {
26
- "@openfn/buildtools": "^1.0.2",
27
27
  "@openfn/simple-ast": "0.4.1",
28
28
  "assertion-error": "2.0.0",
29
29
  "chai": "4.3.6",
@@ -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";
83
- export { dataPath, dataValue, dateFns, each, field, fields, fn, lastReferenceValue, merge, sourceValue, parseCsv } from "@openfn/language-common";
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";
124
+ export { cursor, 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>;