@openstax/ts-utils 1.40.2 → 1.41.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/cjs/services/accountsGateway/index.d.ts +3 -0
  2. package/dist/cjs/services/accountsGateway/index.js +1 -0
  3. package/dist/cjs/services/documentStore/dynamoEncoding.js +2 -2
  4. package/dist/cjs/services/documentStore/unversioned/file-system.d.ts +0 -1
  5. package/dist/cjs/services/documentStore/unversioned/file-system.js +0 -19
  6. package/dist/cjs/services/documentStore/versioned/file-system.d.ts +0 -1
  7. package/dist/cjs/services/documentStore/versioned/file-system.js +1 -12
  8. package/dist/cjs/services/lrsGateway/batching.d.ts +46 -0
  9. package/dist/cjs/services/lrsGateway/batching.js +106 -0
  10. package/dist/cjs/services/lrsGateway/file-system.js +52 -2
  11. package/dist/cjs/services/lrsGateway/index.d.ts +13 -0
  12. package/dist/cjs/services/lrsGateway/index.js +151 -55
  13. package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
  14. package/dist/esm/services/accountsGateway/index.d.ts +3 -0
  15. package/dist/esm/services/accountsGateway/index.js +1 -0
  16. package/dist/esm/services/documentStore/dynamoEncoding.js +2 -2
  17. package/dist/esm/services/documentStore/unversioned/file-system.d.ts +0 -1
  18. package/dist/esm/services/documentStore/unversioned/file-system.js +0 -19
  19. package/dist/esm/services/documentStore/versioned/file-system.d.ts +0 -1
  20. package/dist/esm/services/documentStore/versioned/file-system.js +1 -12
  21. package/dist/esm/services/lrsGateway/batching.d.ts +46 -0
  22. package/dist/esm/services/lrsGateway/batching.js +102 -0
  23. package/dist/esm/services/lrsGateway/file-system.js +52 -2
  24. package/dist/esm/services/lrsGateway/index.d.ts +13 -0
  25. package/dist/esm/services/lrsGateway/index.js +151 -22
  26. package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
  27. package/package.json +1 -1
  28. package/dist/cjs/services/documentStore/fileSystemAssert.d.ts +0 -1
  29. package/dist/cjs/services/documentStore/fileSystemAssert.js +0 -14
  30. package/dist/esm/services/documentStore/fileSystemAssert.d.ts +0 -1
  31. package/dist/esm/services/documentStore/fileSystemAssert.js +0 -10
@@ -1,4 +1,3 @@
1
- import * as queryString from 'query-string';
2
1
  import { once } from '../..';
3
2
  import { assertDefined } from '../../assertions';
4
3
  import { resolveConfigValue } from '../../config';
@@ -8,6 +7,7 @@ import { ifDefined } from '../../guards';
8
7
  import { retryWithDelay } from '../../misc/helpers';
9
8
  import { METHOD } from '../../routing';
10
9
  import { addStatementDefaultFields } from './addStatementDefaultFields';
10
+ import { RequestBatcher } from './batching';
11
11
  export const lrsGateway = (initializer) => (configProvider) => {
12
12
  const config = configProvider[ifDefined(initializer.configSpace, 'lrs')];
13
13
  const lrsHost = once(() => resolveConfigValue(config.lrsHost));
@@ -19,20 +19,51 @@ export const lrsGateway = (initializer) => (configProvider) => {
19
19
  wait: 1000,
20
20
  status: [502]
21
21
  });
22
+ // Initialize request batcher if batching is enabled
23
+ const enableBatching = initializer.enableBatching !== false;
24
+ const batcher = new RequestBatcher(fetcher, {
25
+ batchEndpoint: '/api/batch',
26
+ getAuthHeader: lrsAuthorization,
27
+ getHost: lrsHost,
28
+ });
29
+ /**
30
+ * Makes a fetch request, optionally through the request batcher.
31
+ * Automatically batches concurrent requests when batching is enabled.
32
+ */
33
+ const makeFetch = async (options) => {
34
+ return enableBatching ? batcher.queueRequest(options) : batcher.singleRequest(options);
35
+ };
36
+ /**
37
+ * Formats an agent parameter into a full XapiAgent object.
38
+ * Accepts either a UUID string or a full XapiAgent object.
39
+ */
40
+ const formatAgent = (agent) => {
41
+ if (typeof agent === 'string') {
42
+ return {
43
+ objectType: 'Agent',
44
+ account: {
45
+ homePage: 'https://openstax.org',
46
+ name: agent,
47
+ },
48
+ };
49
+ }
50
+ return agent;
51
+ };
22
52
  // Note: This method actually uses POST
23
53
  const putXapiStatements = async (statements, user) => {
24
54
  const userObj = user
25
55
  ? { uuid: user }
26
56
  : assertDefined(await authProvider.getUser(), new UnauthorizedError);
27
57
  const statementsWithDefaults = statements.map(statement => addStatementDefaultFields(statement, userObj));
28
- const response = await fetcher((await lrsHost()).replace(/\/+$/, '') + '/data/xAPI/statements', {
29
- body: JSON.stringify(statementsWithDefaults),
58
+ const response = await makeFetch({
59
+ path: '/data/xAPI/statements',
60
+ method: METHOD.POST,
30
61
  headers: {
31
62
  Authorization: await lrsAuthorization(),
32
63
  'Content-Type': 'application/json',
33
64
  'X-Experience-API-Version': '1.0.0',
34
65
  },
35
- method: METHOD.POST,
66
+ body: JSON.stringify(statementsWithDefaults),
36
67
  });
37
68
  if (![200, 201].includes(response.status)) {
38
69
  throw new Error(`Unexpected LRS POST statements response code ${response.status} with body:
@@ -52,29 +83,51 @@ ${await response.text()}`);
52
83
  }
53
84
  return response.json();
54
85
  };
55
- const getMoreXapiStatements = async (more) => fetcher((await lrsHost()).replace(/\/+$/, '') + more, {
56
- headers: {
57
- Authorization: await lrsAuthorization(),
58
- 'X-Experience-API-Version': '1.0.0',
59
- },
60
- }).then(formatGetXapiStatementsResponse);
61
- const fetchXapiStatements = async ({ user, anyUser, ...options }) => fetcher((await lrsHost()).replace(/\/+$/, '') + '/data/xAPI/statements?' + queryString.stringify({
62
- ...options,
63
- ...(anyUser === true ? {} : {
64
- agent: JSON.stringify({
86
+ const getMoreXapiStatements = async (more) => {
87
+ // 'more' is a full path with query string, so we don't use makeFetch batching here
88
+ const host = await lrsHost();
89
+ return fetcher(host.replace(/\/+$/, '') + more, {
90
+ headers: {
91
+ Authorization: await lrsAuthorization(),
92
+ 'X-Experience-API-Version': '1.0.0',
93
+ },
94
+ }).then(formatGetXapiStatementsResponse);
95
+ };
96
+ const fetchXapiStatements = async ({ user, anyUser, ...options }) => {
97
+ const queryParams = {};
98
+ // Add filter params
99
+ if (options.verb)
100
+ queryParams.verb = options.verb;
101
+ if (options.activity)
102
+ queryParams.activity = options.activity;
103
+ if (options.registration)
104
+ queryParams.registration = options.registration;
105
+ if (options.related_activities !== undefined)
106
+ queryParams.related_activities = String(options.related_activities);
107
+ if (options.since)
108
+ queryParams.since = options.since;
109
+ if (options.until)
110
+ queryParams.until = options.until;
111
+ // Add agent unless anyUser is true
112
+ if (anyUser !== true) {
113
+ queryParams.agent = JSON.stringify({
65
114
  account: {
66
115
  homePage: 'https://openstax.org',
67
116
  name: user || assertDefined(await authProvider.getUser(), new UnauthorizedError()).uuid,
68
117
  },
69
118
  objectType: 'Agent',
70
- }),
71
- })
72
- }), {
73
- headers: {
74
- Authorization: await lrsAuthorization(),
75
- 'X-Experience-API-Version': '1.0.0',
76
- },
77
- });
119
+ });
120
+ }
121
+ return makeFetch({
122
+ path: '/data/xAPI/statements',
123
+ method: METHOD.GET,
124
+ queryParams,
125
+ headers: {
126
+ Authorization: await lrsAuthorization(),
127
+ 'X-Experience-API-Version': '1.0.0',
128
+ },
129
+ });
130
+ };
78
131
  const getXapiStatements = async (params) => {
79
132
  const { ensureSync, ...fetchParams } = params;
80
133
  if (ensureSync) {
@@ -103,11 +156,87 @@ ${await response.text()}`);
103
156
  };
104
157
  return loadRemaining(await getXapiStatements(params));
105
158
  };
159
+ /**
160
+ * Get a single state document from the xAPI State API.
161
+ *
162
+ * @param activityId - The activity ID
163
+ * @param agent - The agent (UUID string or full XapiAgent object)
164
+ * @param stateId - The state document identifier
165
+ * @param registration - Optional registration UUID
166
+ * @returns The state document as a JSON object, or null if not found
167
+ */
168
+ const getState = async (activityId, agent, stateId, registration) => {
169
+ const agentObj = formatAgent(agent);
170
+ const queryParams = {
171
+ activityId,
172
+ agent: JSON.stringify(agentObj),
173
+ stateId,
174
+ };
175
+ if (registration) {
176
+ queryParams.registration = registration;
177
+ }
178
+ const response = await makeFetch({
179
+ path: '/data/xAPI/activities/state',
180
+ method: METHOD.GET,
181
+ queryParams,
182
+ headers: {
183
+ Authorization: await lrsAuthorization(),
184
+ 'X-Experience-API-Version': '1.0.0',
185
+ },
186
+ });
187
+ if (response.status === 404) {
188
+ return null;
189
+ }
190
+ if (response.status !== 200) {
191
+ throw new Error(`Unexpected LRS GET state response code ${response.status} with body:
192
+
193
+ ${await response.text()}`);
194
+ }
195
+ return response.json();
196
+ };
197
+ /**
198
+ * Create or update a state document in the xAPI State API.
199
+ *
200
+ * @param activityId - The activity ID
201
+ * @param agent - The agent (UUID string or full XapiAgent object)
202
+ * @param stateId - The state document identifier
203
+ * @param body - The state document content (JSON object)
204
+ * @param registration - Optional registration UUID
205
+ */
206
+ const setState = async (activityId, agent, stateId, body, registration) => {
207
+ const agentObj = formatAgent(agent);
208
+ const queryParams = {
209
+ activityId,
210
+ agent: JSON.stringify(agentObj),
211
+ stateId,
212
+ };
213
+ if (registration) {
214
+ queryParams.registration = registration;
215
+ }
216
+ const response = await makeFetch({
217
+ path: '/data/xAPI/activities/state',
218
+ method: METHOD.PUT,
219
+ queryParams,
220
+ headers: {
221
+ Authorization: await lrsAuthorization(),
222
+ 'Content-Type': 'application/json',
223
+ 'X-Experience-API-Version': '1.0.0',
224
+ },
225
+ body: JSON.stringify(body),
226
+ });
227
+ if (response.status !== 204) {
228
+ throw new Error(`Unexpected LRS PUT state response code ${response.status} with body:
229
+
230
+ ${await response.text()}`);
231
+ }
232
+ };
106
233
  return {
107
234
  putXapiStatements,
108
235
  getXapiStatements,
109
236
  getMoreXapiStatements,
110
237
  getAllXapiStatements,
238
+ getState,
239
+ setState,
111
240
  };
112
241
  };
113
242
  };