@openfn/language-kobotoolbox 3.0.5 → 4.1.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
@@ -43,18 +43,33 @@
43
43
  "options"
44
44
  ],
45
45
  "docs": {
46
- "description": "Get submissions for a specific form. Calls `/api/v2/assets/<formId>/data/`.",
46
+ "description": "Get submissions for a specific form. Calls `/api/v2/assets/<formId>/data/`",
47
47
  "tags": [
48
48
  {
49
49
  "title": "example",
50
- "description": "getSubmissions('aXecHjmbATuF6iGFmvBLBX');",
50
+ "description": "getSubmissions('aXecHjmbATuF6iGFmvBLBX');.",
51
+ "caption": "Get submissions for a specific form"
52
+ },
53
+ {
54
+ "title": "example",
55
+ "description": "getSubmissions('aXecHjmbATuF6iGFmvBLBX', { limit: Infinity });",
51
56
  "caption": "Get all submissions for a specific form"
52
57
  },
53
58
  {
54
59
  "title": "example",
55
- "description": "getSubmissions('aXecHjmbATuF6iGFmvBLBX', { query: { _submission_time:{ $gte: \"2022-06-12T21:54:20\" } } });",
60
+ "description": "getSubmissions('aXecHjmbATuF6iGFmvBLBX', { query: { _submission_time:{ $gte: \"2025-03-12T21:54:20\" } } });",
56
61
  "caption": "Get form submissions with a query"
57
62
  },
63
+ {
64
+ "title": "example",
65
+ "description": "getSubmissions('aXecHjmbATuF6iGFmvBLBX', { sort: { _submission_time: -1 } });",
66
+ "caption": "Get form submissions with sorting"
67
+ },
68
+ {
69
+ "title": "example",
70
+ "description": "getSubmissions('aXecHjmbATuF6iGFmvBLBX', { start: 10 });",
71
+ "caption": "Get form submissions with specific start index"
72
+ },
58
73
  {
59
74
  "title": "function",
60
75
  "description": null,
@@ -76,7 +91,7 @@
76
91
  },
77
92
  {
78
93
  "title": "param",
79
- "description": "Optional query params for the request",
94
+ "description": "Options to control the request",
80
95
  "type": {
81
96
  "type": "OptionalType",
82
97
  "expression": {
@@ -87,6 +102,69 @@
87
102
  "name": "options",
88
103
  "default": "{}"
89
104
  },
105
+ {
106
+ "title": "param",
107
+ "description": "Field and direction to sort submissions by.",
108
+ "type": {
109
+ "type": "OptionalType",
110
+ "expression": {
111
+ "type": "NameExpression",
112
+ "name": "object"
113
+ }
114
+ },
115
+ "name": "options.sort"
116
+ },
117
+ {
118
+ "title": "param",
119
+ "description": "Query options to filter the submissions. See query operators {@link http://docs.mongodb.org/manual/reference/operator/query/.}",
120
+ "type": {
121
+ "type": "OptionalType",
122
+ "expression": {
123
+ "type": "NameExpression",
124
+ "name": "object"
125
+ }
126
+ },
127
+ "name": "options.query"
128
+ },
129
+ {
130
+ "title": "param",
131
+ "description": "The index of the first submission to return.",
132
+ "type": {
133
+ "type": "OptionalType",
134
+ "expression": {
135
+ "type": "NameExpression",
136
+ "name": "number"
137
+ }
138
+ },
139
+ "name": "options.start",
140
+ "default": "0"
141
+ },
142
+ {
143
+ "title": "param",
144
+ "description": "Maximum number of submissions to fetch. Pass Infinity to disable the limit and download all submissions",
145
+ "type": {
146
+ "type": "OptionalType",
147
+ "expression": {
148
+ "type": "NameExpression",
149
+ "name": "number"
150
+ }
151
+ },
152
+ "name": "options.limit",
153
+ "default": "30000"
154
+ },
155
+ {
156
+ "title": "param",
157
+ "description": "Limits the size of each page of submissions. Maximum value is 30000.",
158
+ "type": {
159
+ "type": "OptionalType",
160
+ "expression": {
161
+ "type": "NameExpression",
162
+ "name": "number"
163
+ }
164
+ },
165
+ "name": "options.pageSize",
166
+ "default": "10000"
167
+ },
90
168
  {
91
169
  "title": "state",
92
170
  "description": "data - an array of submission objects"
@@ -101,7 +179,7 @@
101
179
  }
102
180
  ]
103
181
  },
104
- "valid": true
182
+ "valid": false
105
183
  },
106
184
  {
107
185
  "name": "getDeploymentInfo",
package/dist/index.cjs CHANGED
@@ -58,7 +58,6 @@ __export(Adaptor_exports, {
58
58
  getForms: () => getForms,
59
59
  getSubmissions: () => getSubmissions,
60
60
  group: () => import_language_common3.group,
61
- http: () => import_language_common3.http,
62
61
  lastReferenceValue: () => import_language_common3.lastReferenceValue,
63
62
  merge: () => import_language_common3.merge,
64
63
  sourceValue: () => import_language_common3.sourceValue
@@ -66,36 +65,29 @@ __export(Adaptor_exports, {
66
65
  var import_language_common2 = require("@openfn/language-common");
67
66
  var import_util2 = require("@openfn/language-common/util");
68
67
 
69
- // src/Utils.js
68
+ // src/util.js
70
69
  var import_language_common = require("@openfn/language-common");
71
70
  var import_util = require("@openfn/language-common/util");
72
- var prepareNextState = (state, response) => {
71
+ var DEFAULT_LIMIT = 3e4;
72
+ var DEFAULT_PAGE_SIZE = 1e4;
73
+ function prepareNextState(state, response) {
73
74
  const { body, ...responseWithoutBody } = response;
74
75
  return {
75
- ...(0, import_language_common.composeNextState)(state, response.body),
76
+ ...(0, import_language_common.composeNextState)(state, body),
76
77
  response: responseWithoutBody
77
78
  };
78
- };
79
- async function request(state, method, path, opts) {
80
- const { baseURL, apiVersion, username, password } = state.configuration;
81
- let baseUrl;
82
- if (!state.configuration.baseUrl && baseURL) {
83
- baseUrl = baseURL;
84
- console.warn(
85
- "No baseUrl found in state.configuration. baseURL will be used instead, but this will be deprecated in the future."
86
- );
87
- } else {
88
- baseUrl = state.configuration.baseUrl;
89
- }
79
+ }
80
+ async function request(state, method, path, opts = {}) {
81
+ const { baseUrl, apiVersion, username, password } = state.configuration;
90
82
  const {
91
83
  data = {},
92
84
  query = {},
93
85
  headers = {},
94
86
  parseAs = "json",
95
- paginate = false
87
+ maxRedirections
96
88
  } = opts;
89
+ const requestPath = `/api/${apiVersion}/${path}/`;
97
90
  const authHeaders = (0, import_util.makeBasicAuthHeader)(username, password);
98
- let start, limit;
99
91
  const options = {
100
92
  body: data,
101
93
  headers: {
@@ -107,34 +99,60 @@ async function request(state, method, path, opts) {
107
99
  format: "json",
108
100
  ...query
109
101
  },
102
+ maxRedirections,
110
103
  parseAs,
111
- baseUrl: `${baseUrl}/api/${apiVersion}`
104
+ baseUrl
112
105
  };
113
- if (paginate) {
114
- const results = [];
115
- do {
116
- const response = await (0, import_util.request)(method, path, options).then(
117
- import_util.logResponse
106
+ return (0, import_util.request)(method, requestPath, options).then(import_util.logResponse);
107
+ }
108
+ async function requestWithPagination(state, path, options = {}) {
109
+ var _a, _b, _c, _d;
110
+ const results = [];
111
+ let { pageSize = DEFAULT_PAGE_SIZE, start, limit, ...otherOptions } = options;
112
+ const maxResults = limit ?? DEFAULT_LIMIT;
113
+ let isFirstRequest = true;
114
+ let requestOptions = { query: { start, limit: pageSize }, ...otherOptions };
115
+ let shouldFetchMoreContent = false;
116
+ const didUserPassLimit = Boolean(limit);
117
+ do {
118
+ requestOptions.query ?? (requestOptions.query = {});
119
+ if (start) {
120
+ requestOptions.query.start = start;
121
+ }
122
+ if (didUserPassLimit || !isFirstRequest) {
123
+ requestOptions.query.limit = Math.min(
124
+ pageSize || maxResults,
125
+ maxResults - results.length
118
126
  );
127
+ } else if (!isNaN(pageSize)) {
128
+ requestOptions.query.limit = pageSize;
129
+ }
130
+ const response = await request(state, "GET", path, requestOptions);
131
+ if ((_a = response.body) == null ? void 0 : _a.results) {
119
132
  results.push(...response.body.results);
120
- if (response.body.next) {
121
- const nextUrl = new URL(response.body.next);
122
- const startDigit = nextUrl.searchParams.get("start") !== null ? nextUrl.searchParams.get("start") : 0;
123
- start = Number(startDigit);
124
- limit = nextUrl.searchParams.get("limit");
125
- options.query = {
126
- ...options.query,
127
- start,
128
- limit
129
- };
130
- } else {
131
- break;
133
+ if ((_b = response == null ? void 0 : response.body) == null ? void 0 : _b.next) {
134
+ const nextUrl = new URL((_c = response == null ? void 0 : response.body) == null ? void 0 : _c.next);
135
+ start = nextUrl.searchParams.get("start");
132
136
  }
133
- } while (true);
134
- return { results };
135
- } else {
136
- return (0, import_util.request)(method, path, options).then(import_util.logResponse);
137
- }
137
+ } else {
138
+ results.push(response.body);
139
+ }
140
+ if (isFirstRequest && !pageSize) {
141
+ pageSize = results.length;
142
+ }
143
+ isFirstRequest = false;
144
+ const hasMoreContent = (_d = response.body.next) == null ? void 0 : _d.includes("start=");
145
+ if (hasMoreContent && didUserPassLimit && results.length === maxResults) {
146
+ console.warn(
147
+ `Warning: The default maximum number of items has been reached (${maxResults}), but more items are available on the server. To download all available items, make another request with start=${maxResults + 1} or set max to Infinity`
148
+ );
149
+ }
150
+ shouldFetchMoreContent = results.length < maxResults && hasMoreContent;
151
+ } while (shouldFetchMoreContent);
152
+ return results;
153
+ }
154
+ function maybeStringify(query) {
155
+ return typeof query === "string" ? query : JSON.stringify(query);
138
156
  }
139
157
 
140
158
  // src/Adaptor.js
@@ -153,34 +171,43 @@ function execute(...operations) {
153
171
  }
154
172
  function getForms() {
155
173
  return async (state) => {
156
- const url = `/assets/?asset_type=survey`;
157
- const response = await request(state, "GET", url, {});
158
- console.log("\u2713", response.body.results.length, "forms fetched.");
174
+ var _a;
175
+ const response = await request(state, "GET", "assets", {
176
+ query: { asset_type: "survey" }
177
+ });
178
+ console.log("\u2713", (_a = response.body.results) == null ? void 0 : _a.length, "forms fetched.");
159
179
  return prepareNextState(state, response);
160
180
  };
161
181
  }
162
- function getSubmissions(formId, options = {}) {
182
+ function getSubmissions(formId, options) {
163
183
  return async (state) => {
164
- const [resolvedFormId, resolvedOptions] = (0, import_util2.expandReferences)(
184
+ const [resolvedFormId, resolvedOptions = {}] = (0, import_util2.expandReferences)(
165
185
  state,
166
186
  formId,
167
187
  options
168
188
  );
169
- const url = `/assets/${resolvedFormId}/data/`;
170
- const query = {};
171
- if (resolvedOptions.query) {
172
- if (typeof resolvedOptions.query == "string") {
173
- query.query = resolvedOptions.query;
174
- } else {
175
- query.query = JSON.stringify(resolvedOptions.query);
176
- }
189
+ const { query, limit, pageSize, sort, start } = resolvedOptions;
190
+ const path = `/assets/${resolvedFormId}/data/`;
191
+ const qs = {};
192
+ if (query) {
193
+ qs.query = maybeStringify(query);
177
194
  }
178
- const { results } = await request(state, "GET", url, {
179
- paginate: true,
180
- query
181
- });
182
- console.log("\u2713", results.length, "submissions fetched.");
183
- return (0, import_language_common2.composeNextState)(state, results);
195
+ if (sort) {
196
+ qs.sort = maybeStringify(sort);
197
+ }
198
+ const requestOptions = {
199
+ query: { ...qs },
200
+ start,
201
+ limit,
202
+ pageSize
203
+ };
204
+ const result = await requestWithPagination(
205
+ state,
206
+ path,
207
+ requestOptions
208
+ );
209
+ console.log("\u2713", result == null ? void 0 : result.length, "submissions fetched.");
210
+ return (0, import_language_common2.composeNextState)(state, result);
184
211
  };
185
212
  }
186
213
  function getDeploymentInfo(formId) {
@@ -198,12 +225,25 @@ var http_exports = {};
198
225
  __export(http_exports, {
199
226
  get: () => get,
200
227
  post: () => post,
201
- put: () => put
228
+ put: () => put,
229
+ request: () => request2
202
230
  });
203
231
  var import_util3 = require("@openfn/language-common/util");
204
- function get(path, options = {}) {
232
+ function request2(method, path, options = {}) {
233
+ return async (state) => {
234
+ const [resolvedMethod, resolvedPath, resolvedOptions = {}] = (0, import_util3.expandReferences)(state, method, path, options);
235
+ const response = await request(
236
+ state,
237
+ resolvedMethod,
238
+ resolvedPath,
239
+ resolvedOptions
240
+ );
241
+ return prepareNextState(state, response);
242
+ };
243
+ }
244
+ function get(path, options) {
205
245
  return async (state) => {
206
- const [resolvedPath, resolvedOptions] = (0, import_util3.expandReferences)(
246
+ const [resolvedPath, resolvedOptions = {}] = (0, import_util3.expandReferences)(
207
247
  state,
208
248
  path,
209
249
  options
@@ -217,9 +257,9 @@ function get(path, options = {}) {
217
257
  return prepareNextState(state, response);
218
258
  };
219
259
  }
220
- function post(path, data, options = {}) {
260
+ function post(path, data, options) {
221
261
  return async (state) => {
222
- const [resolvedPath, resolvedData, resolvedOptions] = (0, import_util3.expandReferences)(
262
+ const [resolvedPath, resolvedData, resolvedOptions = {}] = (0, import_util3.expandReferences)(
223
263
  state,
224
264
  path,
225
265
  data,
@@ -238,9 +278,9 @@ function post(path, data, options = {}) {
238
278
  return prepareNextState(state, response);
239
279
  };
240
280
  }
241
- function put(path, data, options = {}) {
281
+ function put(path, data, options) {
242
282
  return async (state) => {
243
- const [resolvedPath, resolvedData, resolvedOptions] = (0, import_util3.expandReferences)(
283
+ const [resolvedPath, resolvedData, resolvedOptions = {}] = (0, import_util3.expandReferences)(
244
284
  state,
245
285
  path,
246
286
  data,
package/dist/index.js CHANGED
@@ -21,7 +21,6 @@ __export(Adaptor_exports, {
21
21
  getForms: () => getForms,
22
22
  getSubmissions: () => getSubmissions,
23
23
  group: () => group,
24
- http: () => http,
25
24
  lastReferenceValue: () => lastReferenceValue,
26
25
  merge: () => merge,
27
26
  sourceValue: () => sourceValue
@@ -32,40 +31,33 @@ import {
32
31
  } from "@openfn/language-common";
33
32
  import { expandReferences } from "@openfn/language-common/util";
34
33
 
35
- // src/Utils.js
34
+ // src/util.js
36
35
  import { composeNextState } from "@openfn/language-common";
37
36
  import {
38
37
  request as commonRequest,
39
38
  makeBasicAuthHeader,
40
39
  logResponse
41
40
  } from "@openfn/language-common/util";
42
- var prepareNextState = (state, response) => {
41
+ var DEFAULT_LIMIT = 3e4;
42
+ var DEFAULT_PAGE_SIZE = 1e4;
43
+ function prepareNextState(state, response) {
43
44
  const { body, ...responseWithoutBody } = response;
44
45
  return {
45
- ...composeNextState(state, response.body),
46
+ ...composeNextState(state, body),
46
47
  response: responseWithoutBody
47
48
  };
48
- };
49
- async function request(state, method, path, opts) {
50
- const { baseURL, apiVersion, username, password } = state.configuration;
51
- let baseUrl;
52
- if (!state.configuration.baseUrl && baseURL) {
53
- baseUrl = baseURL;
54
- console.warn(
55
- "No baseUrl found in state.configuration. baseURL will be used instead, but this will be deprecated in the future."
56
- );
57
- } else {
58
- baseUrl = state.configuration.baseUrl;
59
- }
49
+ }
50
+ async function request(state, method, path, opts = {}) {
51
+ const { baseUrl, apiVersion, username, password } = state.configuration;
60
52
  const {
61
53
  data = {},
62
54
  query = {},
63
55
  headers = {},
64
56
  parseAs = "json",
65
- paginate = false
57
+ maxRedirections
66
58
  } = opts;
59
+ const requestPath = `/api/${apiVersion}/${path}/`;
67
60
  const authHeaders = makeBasicAuthHeader(username, password);
68
- let start, limit;
69
61
  const options = {
70
62
  body: data,
71
63
  headers: {
@@ -77,34 +69,60 @@ async function request(state, method, path, opts) {
77
69
  format: "json",
78
70
  ...query
79
71
  },
72
+ maxRedirections,
80
73
  parseAs,
81
- baseUrl: `${baseUrl}/api/${apiVersion}`
74
+ baseUrl
82
75
  };
83
- if (paginate) {
84
- const results = [];
85
- do {
86
- const response = await commonRequest(method, path, options).then(
87
- logResponse
76
+ return commonRequest(method, requestPath, options).then(logResponse);
77
+ }
78
+ async function requestWithPagination(state, path, options = {}) {
79
+ var _a, _b, _c, _d;
80
+ const results = [];
81
+ let { pageSize = DEFAULT_PAGE_SIZE, start, limit, ...otherOptions } = options;
82
+ const maxResults = limit ?? DEFAULT_LIMIT;
83
+ let isFirstRequest = true;
84
+ let requestOptions = { query: { start, limit: pageSize }, ...otherOptions };
85
+ let shouldFetchMoreContent = false;
86
+ const didUserPassLimit = Boolean(limit);
87
+ do {
88
+ requestOptions.query ?? (requestOptions.query = {});
89
+ if (start) {
90
+ requestOptions.query.start = start;
91
+ }
92
+ if (didUserPassLimit || !isFirstRequest) {
93
+ requestOptions.query.limit = Math.min(
94
+ pageSize || maxResults,
95
+ maxResults - results.length
88
96
  );
97
+ } else if (!isNaN(pageSize)) {
98
+ requestOptions.query.limit = pageSize;
99
+ }
100
+ const response = await request(state, "GET", path, requestOptions);
101
+ if ((_a = response.body) == null ? void 0 : _a.results) {
89
102
  results.push(...response.body.results);
90
- if (response.body.next) {
91
- const nextUrl = new URL(response.body.next);
92
- const startDigit = nextUrl.searchParams.get("start") !== null ? nextUrl.searchParams.get("start") : 0;
93
- start = Number(startDigit);
94
- limit = nextUrl.searchParams.get("limit");
95
- options.query = {
96
- ...options.query,
97
- start,
98
- limit
99
- };
100
- } else {
101
- break;
103
+ if ((_b = response == null ? void 0 : response.body) == null ? void 0 : _b.next) {
104
+ const nextUrl = new URL((_c = response == null ? void 0 : response.body) == null ? void 0 : _c.next);
105
+ start = nextUrl.searchParams.get("start");
102
106
  }
103
- } while (true);
104
- return { results };
105
- } else {
106
- return commonRequest(method, path, options).then(logResponse);
107
- }
107
+ } else {
108
+ results.push(response.body);
109
+ }
110
+ if (isFirstRequest && !pageSize) {
111
+ pageSize = results.length;
112
+ }
113
+ isFirstRequest = false;
114
+ const hasMoreContent = (_d = response.body.next) == null ? void 0 : _d.includes("start=");
115
+ if (hasMoreContent && didUserPassLimit && results.length === maxResults) {
116
+ console.warn(
117
+ `Warning: The default maximum number of items has been reached (${maxResults}), but more items are available on the server. To download all available items, make another request with start=${maxResults + 1} or set max to Infinity`
118
+ );
119
+ }
120
+ shouldFetchMoreContent = results.length < maxResults && hasMoreContent;
121
+ } while (shouldFetchMoreContent);
122
+ return results;
123
+ }
124
+ function maybeStringify(query) {
125
+ return typeof query === "string" ? query : JSON.stringify(query);
108
126
  }
109
127
 
110
128
  // src/Adaptor.js
@@ -118,7 +136,6 @@ import {
118
136
  fields,
119
137
  fn,
120
138
  fnIf,
121
- http,
122
139
  group,
123
140
  lastReferenceValue,
124
141
  merge,
@@ -138,34 +155,43 @@ function execute(...operations) {
138
155
  }
139
156
  function getForms() {
140
157
  return async (state) => {
141
- const url = `/assets/?asset_type=survey`;
142
- const response = await request(state, "GET", url, {});
143
- console.log("\u2713", response.body.results.length, "forms fetched.");
158
+ var _a;
159
+ const response = await request(state, "GET", "assets", {
160
+ query: { asset_type: "survey" }
161
+ });
162
+ console.log("\u2713", (_a = response.body.results) == null ? void 0 : _a.length, "forms fetched.");
144
163
  return prepareNextState(state, response);
145
164
  };
146
165
  }
147
- function getSubmissions(formId, options = {}) {
166
+ function getSubmissions(formId, options) {
148
167
  return async (state) => {
149
- const [resolvedFormId, resolvedOptions] = expandReferences(
168
+ const [resolvedFormId, resolvedOptions = {}] = expandReferences(
150
169
  state,
151
170
  formId,
152
171
  options
153
172
  );
154
- const url = `/assets/${resolvedFormId}/data/`;
155
- const query = {};
156
- if (resolvedOptions.query) {
157
- if (typeof resolvedOptions.query == "string") {
158
- query.query = resolvedOptions.query;
159
- } else {
160
- query.query = JSON.stringify(resolvedOptions.query);
161
- }
173
+ const { query, limit, pageSize, sort, start } = resolvedOptions;
174
+ const path = `/assets/${resolvedFormId}/data/`;
175
+ const qs = {};
176
+ if (query) {
177
+ qs.query = maybeStringify(query);
162
178
  }
163
- const { results } = await request(state, "GET", url, {
164
- paginate: true,
165
- query
166
- });
167
- console.log("\u2713", results.length, "submissions fetched.");
168
- return composeNextState2(state, results);
179
+ if (sort) {
180
+ qs.sort = maybeStringify(sort);
181
+ }
182
+ const requestOptions = {
183
+ query: { ...qs },
184
+ start,
185
+ limit,
186
+ pageSize
187
+ };
188
+ const result = await requestWithPagination(
189
+ state,
190
+ path,
191
+ requestOptions
192
+ );
193
+ console.log("\u2713", result == null ? void 0 : result.length, "submissions fetched.");
194
+ return composeNextState2(state, result);
169
195
  };
170
196
  }
171
197
  function getDeploymentInfo(formId) {
@@ -183,12 +209,25 @@ var http_exports = {};
183
209
  __export(http_exports, {
184
210
  get: () => get,
185
211
  post: () => post,
186
- put: () => put
212
+ put: () => put,
213
+ request: () => request2
187
214
  });
188
215
  import { expandReferences as expandReferences2 } from "@openfn/language-common/util";
189
- function get(path, options = {}) {
216
+ function request2(method, path, options = {}) {
217
+ return async (state) => {
218
+ const [resolvedMethod, resolvedPath, resolvedOptions = {}] = expandReferences2(state, method, path, options);
219
+ const response = await request(
220
+ state,
221
+ resolvedMethod,
222
+ resolvedPath,
223
+ resolvedOptions
224
+ );
225
+ return prepareNextState(state, response);
226
+ };
227
+ }
228
+ function get(path, options) {
190
229
  return async (state) => {
191
- const [resolvedPath, resolvedOptions] = expandReferences2(
230
+ const [resolvedPath, resolvedOptions = {}] = expandReferences2(
192
231
  state,
193
232
  path,
194
233
  options
@@ -202,9 +241,9 @@ function get(path, options = {}) {
202
241
  return prepareNextState(state, response);
203
242
  };
204
243
  }
205
- function post(path, data, options = {}) {
244
+ function post(path, data, options) {
206
245
  return async (state) => {
207
- const [resolvedPath, resolvedData, resolvedOptions] = expandReferences2(
246
+ const [resolvedPath, resolvedData, resolvedOptions = {}] = expandReferences2(
208
247
  state,
209
248
  path,
210
249
  data,
@@ -223,9 +262,9 @@ function post(path, data, options = {}) {
223
262
  return prepareNextState(state, response);
224
263
  };
225
264
  }
226
- function put(path, data, options = {}) {
265
+ function put(path, data, options) {
227
266
  return async (state) => {
228
- const [resolvedPath, resolvedData, resolvedOptions] = expandReferences2(
267
+ const [resolvedPath, resolvedData, resolvedOptions = {}] = expandReferences2(
229
268
  state,
230
269
  path,
231
270
  data,
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@openfn/language-kobotoolbox",
3
- "version": "3.0.5",
4
- "description": "A Kobo Toolbox Language Pack for OpenFn",
3
+ "label": "KoboToolbox",
4
+ "version": "4.1.0",
5
+ "description": "A KoboToolbox Language Pack for OpenFn",
5
6
  "homepage": "https://docs.openfn.org",
6
7
  "repository": {
7
8
  "type": "git",
@@ -21,9 +22,8 @@
21
22
  },
22
23
  "devDependencies": {
23
24
  "assertion-error": "^1.0.1",
24
- "chai": "^3.4.0",
25
+ "chai": "^5.2.0",
25
26
  "deep-eql": "^0.1.3",
26
- "esno": "^0.16.3",
27
27
  "rimraf": "^3.0.2"
28
28
  },
29
29
  "type": "module",
@@ -38,8 +38,9 @@
38
38
  },
39
39
  "scripts": {
40
40
  "build": "pnpm clean && build-adaptor kobotoolbox",
41
- "test": "mocha --experimental-specifier-resolution=node --no-warnings",
42
- "test:watch": "mocha -w --experimental-specifier-resolution=node --no-warnings",
41
+ "test": "mocha --experimental-specifier-resolution=node --no-warnings --exclude test/integration.js --recursive",
42
+ "test:watch": "mocha -w --experimental-specifier-resolution=node --no-warnings --exclude test/integration.js --recursive",
43
+ "test:integration": "mocha --experimental-specifier-resolution=node --no-warnings test/integration.js",
43
44
  "clean": "rimraf dist types docs",
44
45
  "pack": "pnpm pack --pack-destination ../../dist",
45
46
  "lint": "eslint src"
@@ -1,10 +1,3 @@
1
- /**
2
- * Options object
3
- * @typedef {Object} RequestOptions
4
- * @property {object} query - An object of query parameters to be encoded into the URL
5
- * @property {object} headers - An object of all request headers
6
- * @property {string} [parseAs='json'] - The response format to parse (e.g., 'json', 'text', or 'stream')
7
- */
8
1
  /**
9
2
  * Execute a sequence of operations.
10
3
  * Wraps `language-common/execute`, and prepends initial state for http.
@@ -29,19 +22,36 @@ export function execute(...operations: Operations): Operation;
29
22
  */
30
23
  export function getForms(): Operation;
31
24
  /**
32
- * Get submissions for a specific form. Calls `/api/v2/assets/<formId>/data/`.
25
+ * Get submissions for a specific form. Calls `/api/v2/assets/<formId>/data/`
26
+ * @example <caption>Get submissions for a specific form</caption>
27
+ * getSubmissions('aXecHjmbATuF6iGFmvBLBX');.
33
28
  * @example <caption>Get all submissions for a specific form</caption>
34
- * getSubmissions('aXecHjmbATuF6iGFmvBLBX');
29
+ * getSubmissions('aXecHjmbATuF6iGFmvBLBX', { limit: Infinity });
35
30
  * @example <caption>Get form submissions with a query</caption>
36
- * getSubmissions('aXecHjmbATuF6iGFmvBLBX', { query: { _submission_time:{ $gte: "2022-06-12T21:54:20" } } });
31
+ * getSubmissions('aXecHjmbATuF6iGFmvBLBX', { query: { _submission_time:{ $gte: "2025-03-12T21:54:20" } } });
32
+ * @example <caption>Get form submissions with sorting</caption>
33
+ * getSubmissions('aXecHjmbATuF6iGFmvBLBX', { sort: { _submission_time: -1 } });
34
+ * @example <caption>Get form submissions with specific start index</caption>
35
+ * getSubmissions('aXecHjmbATuF6iGFmvBLBX', { start: 10 });
37
36
  * @function
38
37
  * @public
39
38
  * @param {string} formId - Form Id to get the specific submissions
40
- * @param {object} [options={}] - Optional query params for the request
39
+ * @param {object} [options={}] - Options to control the request
40
+ * @param {object} [options.sort] - Field and direction to sort submissions by.
41
+ * @param {object} [options.query] - Query options to filter the submissions. See query operators {@link http://docs.mongodb.org/manual/reference/operator/query/.}
42
+ * @param {number} [options.start=0] - The index of the first submission to return.
43
+ * @param {number} [options.limit=30000] - Maximum number of submissions to fetch. Pass Infinity to disable the limit and download all submissions
44
+ * @param {number} [options.pageSize=10000] - Limits the size of each page of submissions. Maximum value is 30000.
41
45
  * @state data - an array of submission objects
42
46
  * @returns {Operation}
43
47
  */
44
- export function getSubmissions(formId: string, options?: object): Operation;
48
+ export function getSubmissions(formId: string, options?: {
49
+ sort?: object;
50
+ query?: object;
51
+ start?: number;
52
+ limit?: number;
53
+ pageSize?: number;
54
+ }): Operation;
45
55
  /**
46
56
  * Get deployment information for a specific form. Calls `/api/v2/assets/<id>/deployment/`.
47
57
  * @example
@@ -53,21 +63,4 @@ export function getSubmissions(formId: string, options?: object): Operation;
53
63
  * @returns {Operation}
54
64
  */
55
65
  export function getDeploymentInfo(formId: string): Operation;
56
- /**
57
- * Options object
58
- */
59
- export type RequestOptions = {
60
- /**
61
- * - An object of query parameters to be encoded into the URL
62
- */
63
- query: object;
64
- /**
65
- * - An object of all request headers
66
- */
67
- headers: object;
68
- /**
69
- * - The response format to parse (e.g., 'json', 'text', or 'stream')
70
- */
71
- parseAs?: string;
72
- };
73
- export { alterState, cursor, dataPath, dataValue, each, field, fields, fn, fnIf, http, group, lastReferenceValue, merge, sourceValue } from "@openfn/language-common";
66
+ export { alterState, cursor, dataPath, dataValue, each, field, fields, fn, fnIf, group, lastReferenceValue, merge, sourceValue } from "@openfn/language-common";
package/types/http.d.ts CHANGED
@@ -1,31 +1,52 @@
1
1
  /**
2
2
  * State object
3
- * @typedef {Object} KoboToolboxHttpState
3
+ * @typedef {Object} HttpState
4
+ * @private
4
5
  * @property data - The response body (as JSON)
5
- * @property response - The HTTP response from the KoboToolbox server (excluding the body). Responses will be returned in JSON format
6
- * @property references - An array of all previous data objects used in the Job
6
+ * @property response - The HTTP response from the KoboToolbox server (excluding the body)
7
+ * @property references - An array containing all previous data objects
7
8
  */
8
9
  /**
9
10
  * Options object
10
- * @typedef {Object} RequestOptions
11
+ * @typedef {Object} HTTPRequestOptions
11
12
  * @property {object} query - An object of query parameters to be encoded into the URL
12
13
  * @property {object} headers - An object of all request headers
14
+ * @property {object} body - The request body (as JSON)
15
+ * @property {number} maxRedirections - The maximum number of redirects to follow
13
16
  * @property {string} [parseAs='json'] - The response format to parse (e.g., 'json', 'text', or 'stream')
14
17
  */
18
+ /**
19
+ * Make a HTTP request to any KoboToolbox endpoint
20
+ * @example <caption>Bulk updating of submissions</caption>
21
+ * http.request("PATCH", `assets/${$.form_uid}/data/bulk/`, {
22
+ * body: {
23
+ * submission_ids: [$.data.submission_id],
24
+ * data: {
25
+ * Transaction_status: "success",
26
+ * },
27
+ * },
28
+ * });
29
+ * @function
30
+ * @public
31
+ * @param {string} method - HTTP method to use
32
+ * @param {string} path - Path to resource
33
+ * @param {HTTPRequestOptions} [options={}] - An object containing query, headers, and body for the request
34
+ * @state {HttpState}
35
+ * @returns {Operation}
36
+ */
37
+ export function request(method: string, path: string, options?: HTTPRequestOptions): Operation;
15
38
  /**
16
39
  * Make a GET request to any KoboToolbox endpoint.
17
40
  * @public
18
41
  * @function
19
42
  * @example <caption>GET assets resource</caption>
20
- * http.get(
21
- * "/assets/",
22
- * )
43
+ * http.get('assets')
23
44
  * @param {string} path - path to resource
24
- * @param {RequestOptions} [options={}] - An object containing query params and headers for the request
25
- * @state {KoboToolboxHttpState}
45
+ * @param {HTTPRequestOptions} [options={}] - An object containing query params and headers for the request
46
+ * @state {HttpState}
26
47
  * @returns {operation}
27
48
  */
28
- export function get(path: string, options?: RequestOptions): operation;
49
+ export function get(path: string, options?: HTTPRequestOptions): operation;
29
50
  /**
30
51
  * Make a POST request to a KoboToolbox endpoint
31
52
  * @public
@@ -40,11 +61,11 @@ export function get(path: string, options?: RequestOptions): operation;
40
61
  * );
41
62
  * @param {string} path - path to resource
42
63
  * @param {any} data - the body data in JSON format
43
- * @param {RequestOptions} [options={}] - An object containing query params and headers for the request
44
- * @state {KoboToolboxHttpState}
64
+ * @param {HTTPRequestOptions} [options={}] - An object containing query params and headers for the request
65
+ * @state {HttpState}
45
66
  * @returns {operation}
46
67
  */
47
- export function post(path: string, data: any, options?: RequestOptions): operation;
68
+ export function post(path: string, data: any, options?: HTTPRequestOptions): operation;
48
69
  /**
49
70
  * Make a PUT request to a KoboToolbox endpoint
50
71
  * @public
@@ -59,32 +80,19 @@ export function post(path: string, data: any, options?: RequestOptions): operati
59
80
  * );
60
81
  * @param {string} path - path to resource
61
82
  * @param {any} data - the body data in JSON format
62
- * @param {RequestOptions} [options={}] - An object containing query params and headers for the request
63
- * @state {KoboToolboxHttpState}
83
+ * @param {HTTPRequestOptions} [options={}] - An object containing query params and headers for the request
84
+ * @state {HttpState}
64
85
  * @returns {operation}
65
86
  */
66
- export function put(path: string, data: any, options?: RequestOptions): operation;
87
+ export function put(path: string, data: any, options?: HTTPRequestOptions): operation;
67
88
  /**
68
89
  * State object
69
90
  */
70
- export type KoboToolboxHttpState = {
71
- /**
72
- * - The response body (as JSON)
73
- */
74
- data: any;
75
- /**
76
- * - The HTTP response from the KoboToolbox server (excluding the body). Responses will be returned in JSON format
77
- */
78
- response: any;
79
- /**
80
- * - An array of all previous data objects used in the Job
81
- */
82
- references: any;
83
- };
91
+ export type HttpState = any;
84
92
  /**
85
93
  * Options object
86
94
  */
87
- export type RequestOptions = {
95
+ export type HTTPRequestOptions = {
88
96
  /**
89
97
  * - An object of query parameters to be encoded into the URL
90
98
  */
@@ -93,6 +101,14 @@ export type RequestOptions = {
93
101
  * - An object of all request headers
94
102
  */
95
103
  headers: object;
104
+ /**
105
+ * - The request body (as JSON)
106
+ */
107
+ body: object;
108
+ /**
109
+ * - The maximum number of redirects to follow
110
+ */
111
+ maxRedirections: number;
96
112
  /**
97
113
  * - The response format to parse (e.g., 'json', 'text', or 'stream')
98
114
  */
@@ -0,0 +1,6 @@
1
+ export function prepareNextState(state: any, response: any): any;
2
+ export function request(state: any, method: any, path: any, opts?: {}): Promise<any>;
3
+ export function requestWithPagination(state: any, path: any, options?: {}): Promise<any[]>;
4
+ export function maybeStringify(query: any): string;
5
+ export const DEFAULT_LIMIT: 30000;
6
+ export const DEFAULT_PAGE_SIZE: 10000;
package/types/Utils.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export function request(state: any, method: any, path: any, opts: any): Promise<any>;
2
- export function prepareNextState(state: any, response: any): any;