@timeback/oneroster 0.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/README.md +295 -0
- package/dist/client.d.ts +125 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/constants.d.ts +23 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +1414 -0
- package/dist/factory.d.ts +100 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2614 -0
- package/dist/lib/index.d.ts +11 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/pagination.d.ts +30 -0
- package/dist/lib/pagination.d.ts.map +1 -0
- package/dist/lib/resolve.d.ts +21 -0
- package/dist/lib/resolve.d.ts.map +1 -0
- package/dist/lib/transport.d.ts +32 -0
- package/dist/lib/transport.d.ts.map +1 -0
- package/dist/resources/assessment/index.d.ts +6 -0
- package/dist/resources/assessment/index.d.ts.map +1 -0
- package/dist/resources/assessment/line-items.d.ts +38 -0
- package/dist/resources/assessment/line-items.d.ts.map +1 -0
- package/dist/resources/assessment/results.d.ts +25 -0
- package/dist/resources/assessment/results.d.ts.map +1 -0
- package/dist/resources/base.d.ts +181 -0
- package/dist/resources/base.d.ts.map +1 -0
- package/dist/resources/gradebook/categories.d.ts +16 -0
- package/dist/resources/gradebook/categories.d.ts.map +1 -0
- package/dist/resources/gradebook/index.d.ts +8 -0
- package/dist/resources/gradebook/index.d.ts.map +1 -0
- package/dist/resources/gradebook/line-items.d.ts +52 -0
- package/dist/resources/gradebook/line-items.d.ts.map +1 -0
- package/dist/resources/gradebook/results.d.ts +24 -0
- package/dist/resources/gradebook/results.d.ts.map +1 -0
- package/dist/resources/gradebook/score-scales.d.ts +16 -0
- package/dist/resources/gradebook/score-scales.d.ts.map +1 -0
- package/dist/resources/index.d.ts +11 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/resources/index.d.ts +5 -0
- package/dist/resources/resources/index.d.ts.map +1 -0
- package/dist/resources/resources/resources.d.ts +39 -0
- package/dist/resources/resources/resources.d.ts.map +1 -0
- package/dist/resources/rostering/academic-sessions.d.ts +97 -0
- package/dist/resources/rostering/academic-sessions.d.ts.map +1 -0
- package/dist/resources/rostering/classes.d.ts +276 -0
- package/dist/resources/rostering/classes.d.ts.map +1 -0
- package/dist/resources/rostering/courses.d.ts +75 -0
- package/dist/resources/rostering/courses.d.ts.map +1 -0
- package/dist/resources/rostering/demographics.d.ts +18 -0
- package/dist/resources/rostering/demographics.d.ts.map +1 -0
- package/dist/resources/rostering/enrollments.d.ts +28 -0
- package/dist/resources/rostering/enrollments.d.ts.map +1 -0
- package/dist/resources/rostering/index.d.ts +12 -0
- package/dist/resources/rostering/index.d.ts.map +1 -0
- package/dist/resources/rostering/orgs.d.ts +18 -0
- package/dist/resources/rostering/orgs.d.ts.map +1 -0
- package/dist/resources/rostering/schools.d.ts +214 -0
- package/dist/resources/rostering/schools.d.ts.map +1 -0
- package/dist/resources/rostering/users.d.ts +191 -0
- package/dist/resources/rostering/users.d.ts.map +1 -0
- package/dist/types/base.d.ts +127 -0
- package/dist/types/base.d.ts.map +1 -0
- package/dist/types/callable.d.ts +246 -0
- package/dist/types/callable.d.ts.map +1 -0
- package/dist/types/client.d.ts +58 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/filters.d.ts +165 -0
- package/dist/types/filters.d.ts.map +1 -0
- package/dist/types/gradebook.d.ts +262 -0
- package/dist/types/gradebook.d.ts.map +1 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/resources.d.ts +191 -0
- package/dist/types/resources.d.ts.map +1 -0
- package/dist/types/rostering.d.ts +732 -0
- package/dist/types/rostering.d.ts.map +1 -0
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +0 -0
- package/dist/utils.d.ts +51 -0
- package/dist/utils.d.ts.map +1 -0
- package/package.json +41 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2614 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// ../../internal/constants/src/endpoints.ts
|
|
21
|
+
var DEFAULT_PLATFORM = "BEYOND_AI";
|
|
22
|
+
var BEYONDAI_TOKEN_URLS = {
|
|
23
|
+
staging: "https://staging-beyond-timeback-api-2-idp.auth.us-east-1.amazoncognito.com/oauth2/token",
|
|
24
|
+
production: "https://prod-beyond-timeback-api-2-idp.auth.us-east-1.amazoncognito.com/oauth2/token"
|
|
25
|
+
};
|
|
26
|
+
var BEYONDAI_API_URLS = {
|
|
27
|
+
staging: "https://api.staging.alpha-1edtech.ai",
|
|
28
|
+
production: "https://api.alpha-1edtech.ai"
|
|
29
|
+
};
|
|
30
|
+
var BEYONDAI_CALIPER_URLS = {
|
|
31
|
+
staging: "https://caliper.staging.alpha-1edtech.ai",
|
|
32
|
+
production: "https://caliper.alpha-1edtech.ai"
|
|
33
|
+
};
|
|
34
|
+
var BEYONDAI_QTI_URLS = {
|
|
35
|
+
staging: "https://qti.alpha-1edtech.ai/api",
|
|
36
|
+
production: "https://qti.alpha-1edtech.ai/api"
|
|
37
|
+
};
|
|
38
|
+
var LEARNWITHAI_TOKEN_URLS = {
|
|
39
|
+
staging: "https://platform.dev.timeback.com/auth/1.0/token",
|
|
40
|
+
production: "https://platform.timeback.com/auth/1.0/token"
|
|
41
|
+
};
|
|
42
|
+
var LEARNWITHAI_API_URLS = {
|
|
43
|
+
staging: "https://platform.dev.timeback.com",
|
|
44
|
+
production: "https://platform.timeback.com"
|
|
45
|
+
};
|
|
46
|
+
var LEARNWITHAI_CALIPER_URLS = {
|
|
47
|
+
staging: "https://platform.dev.timeback.com",
|
|
48
|
+
production: "https://platform.timeback.com"
|
|
49
|
+
};
|
|
50
|
+
var LEARNWITHAI_QTI_URLS = {
|
|
51
|
+
staging: "https://platform.dev.timeback.com",
|
|
52
|
+
production: "https://platform.timeback.com"
|
|
53
|
+
};
|
|
54
|
+
var PLATFORM_ENDPOINTS = {
|
|
55
|
+
BEYOND_AI: {
|
|
56
|
+
token: BEYONDAI_TOKEN_URLS,
|
|
57
|
+
api: BEYONDAI_API_URLS,
|
|
58
|
+
caliper: BEYONDAI_CALIPER_URLS,
|
|
59
|
+
qti: BEYONDAI_QTI_URLS
|
|
60
|
+
},
|
|
61
|
+
LEARNWITH_AI: {
|
|
62
|
+
token: LEARNWITHAI_TOKEN_URLS,
|
|
63
|
+
api: LEARNWITHAI_API_URLS,
|
|
64
|
+
caliper: LEARNWITHAI_CALIPER_URLS,
|
|
65
|
+
qti: LEARNWITHAI_QTI_URLS
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
// ../../internal/constants/src/typescript.ts
|
|
69
|
+
var TypeScriptPackages = {
|
|
70
|
+
tsc: "tsc",
|
|
71
|
+
nativePreview: "@typescript/native-preview@7.0.0-dev.20251217.1"
|
|
72
|
+
};
|
|
73
|
+
var TYPESCRIPT_PACKAGE = TypeScriptPackages.nativePreview;
|
|
74
|
+
// ../../internal/logger/src/debug.ts
|
|
75
|
+
var patterns = null;
|
|
76
|
+
var debugAll = false;
|
|
77
|
+
var debugEnvSet = false;
|
|
78
|
+
function patternToRegex(pattern) {
|
|
79
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
80
|
+
const regexStr = escaped.replace(/\*/g, ".*");
|
|
81
|
+
return new RegExp(`^${regexStr}$`);
|
|
82
|
+
}
|
|
83
|
+
function parseDebugEnv() {
|
|
84
|
+
if (patterns !== null)
|
|
85
|
+
return;
|
|
86
|
+
patterns = [];
|
|
87
|
+
if (typeof process === "undefined" || !process.env?.DEBUG) {
|
|
88
|
+
debugEnvSet = false;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
debugEnvSet = true;
|
|
92
|
+
const debugValue = process.env.DEBUG.trim();
|
|
93
|
+
if (debugValue === "1" || debugValue === "true" || debugValue === "*") {
|
|
94
|
+
debugAll = true;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const parts = debugValue.split(",").map((p) => p.trim()).filter(Boolean);
|
|
98
|
+
for (const part of parts) {
|
|
99
|
+
if (part.startsWith("-")) {
|
|
100
|
+
patterns.push({
|
|
101
|
+
regex: patternToRegex(part.slice(1)),
|
|
102
|
+
exclude: true
|
|
103
|
+
});
|
|
104
|
+
} else {
|
|
105
|
+
patterns.push({
|
|
106
|
+
regex: patternToRegex(part),
|
|
107
|
+
exclude: false
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const hasInclude = patterns.some((p) => !p.exclude);
|
|
112
|
+
if (!hasInclude && patterns.length > 0) {
|
|
113
|
+
debugAll = true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function shouldShowDebug(scope) {
|
|
117
|
+
parseDebugEnv();
|
|
118
|
+
if (!debugEnvSet) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
if (debugAll) {
|
|
122
|
+
if (scope) {
|
|
123
|
+
for (const pattern of patterns) {
|
|
124
|
+
if (pattern.exclude && pattern.regex.test(scope)) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
if (!scope) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
for (const pattern of patterns) {
|
|
135
|
+
if (pattern.exclude && pattern.regex.test(scope)) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
for (const pattern of patterns) {
|
|
140
|
+
if (!pattern.exclude && pattern.regex.test(scope)) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ../../internal/logger/src/env.ts
|
|
148
|
+
function isBrowser() {
|
|
149
|
+
return typeof globalThis !== "undefined" && "window" in globalThis;
|
|
150
|
+
}
|
|
151
|
+
function detectEnvironment() {
|
|
152
|
+
if (isBrowser()) {
|
|
153
|
+
return "browser";
|
|
154
|
+
}
|
|
155
|
+
if (typeof process !== "undefined" && process.env) {
|
|
156
|
+
if (process.env["NODE_ENV"] === "test" || process.env["BUN_ENV"] === "test") {
|
|
157
|
+
return "test";
|
|
158
|
+
}
|
|
159
|
+
if (false) {}
|
|
160
|
+
if (process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.CIRCLECI || process.env.JENKINS_URL || process.env.BUILDKITE) {
|
|
161
|
+
return "ci";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return "terminal";
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ../../internal/logger/src/formatters/terminal.ts
|
|
168
|
+
var nodeInspect;
|
|
169
|
+
if (!isBrowser()) {
|
|
170
|
+
try {
|
|
171
|
+
const util = await import("node:util");
|
|
172
|
+
nodeInspect = util.inspect;
|
|
173
|
+
} catch {}
|
|
174
|
+
}
|
|
175
|
+
var colors = {
|
|
176
|
+
reset: "\x1B[0m",
|
|
177
|
+
bold: "\x1B[1m",
|
|
178
|
+
dim: "\x1B[2m",
|
|
179
|
+
red: "\x1B[31m",
|
|
180
|
+
yellow: "\x1B[33m",
|
|
181
|
+
blue: "\x1B[34m",
|
|
182
|
+
cyan: "\x1B[36m"
|
|
183
|
+
};
|
|
184
|
+
function getLevelColor(level) {
|
|
185
|
+
switch (level) {
|
|
186
|
+
case "debug":
|
|
187
|
+
return colors.blue;
|
|
188
|
+
case "info":
|
|
189
|
+
return colors.cyan;
|
|
190
|
+
case "warn":
|
|
191
|
+
return colors.yellow;
|
|
192
|
+
case "error":
|
|
193
|
+
return colors.red;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function getConsoleMethod(level) {
|
|
197
|
+
switch (level) {
|
|
198
|
+
case "debug":
|
|
199
|
+
return console.debug;
|
|
200
|
+
case "info":
|
|
201
|
+
return console.info;
|
|
202
|
+
case "warn":
|
|
203
|
+
return console.warn;
|
|
204
|
+
case "error":
|
|
205
|
+
return console.error;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function formatContext(context) {
|
|
209
|
+
if (nodeInspect) {
|
|
210
|
+
return nodeInspect(context, {
|
|
211
|
+
depth: null,
|
|
212
|
+
colors: true,
|
|
213
|
+
breakLength: 80,
|
|
214
|
+
compact: false
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return JSON.stringify(context, null, 2);
|
|
218
|
+
}
|
|
219
|
+
var terminalFormatter = (entry) => {
|
|
220
|
+
const levelColor = getLevelColor(entry.level);
|
|
221
|
+
const consoleMethod = getConsoleMethod(entry.level);
|
|
222
|
+
const levelUpper = entry.level.toUpperCase().padEnd(5);
|
|
223
|
+
const isoString = entry.timestamp.toISOString().replace(/\.\d{3}Z$/, "");
|
|
224
|
+
const timestamp = `${colors.dim}[${isoString}]${colors.reset}`;
|
|
225
|
+
const level = `${levelColor}${levelUpper}${colors.reset}`;
|
|
226
|
+
const scope = entry.scope ? `${colors.bold}[${entry.scope}]${colors.reset} ` : "";
|
|
227
|
+
const prefix = `${timestamp} ${level} ${scope}${entry.message}`;
|
|
228
|
+
if (entry.context && Object.keys(entry.context).length > 0) {
|
|
229
|
+
consoleMethod(prefix, formatContext(entry.context));
|
|
230
|
+
} else {
|
|
231
|
+
consoleMethod(prefix);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
// ../../internal/logger/src/formatters/ci.ts
|
|
235
|
+
var LEVEL_PREFIX = {
|
|
236
|
+
debug: "[DEBUG]",
|
|
237
|
+
info: "[INFO]",
|
|
238
|
+
warn: "[WARN]",
|
|
239
|
+
error: "[ERROR]"
|
|
240
|
+
};
|
|
241
|
+
function formatContext2(context) {
|
|
242
|
+
return Object.entries(context).map(([key, value]) => `${key}=${formatValue(value)}`).join(" ");
|
|
243
|
+
}
|
|
244
|
+
function formatValue(value) {
|
|
245
|
+
if (typeof value === "string")
|
|
246
|
+
return value;
|
|
247
|
+
if (typeof value === "number")
|
|
248
|
+
return String(value);
|
|
249
|
+
if (typeof value === "boolean")
|
|
250
|
+
return String(value);
|
|
251
|
+
if (value === null)
|
|
252
|
+
return "null";
|
|
253
|
+
if (value === undefined)
|
|
254
|
+
return "undefined";
|
|
255
|
+
return JSON.stringify(value);
|
|
256
|
+
}
|
|
257
|
+
var ciFormatter = (entry) => {
|
|
258
|
+
const parts = [];
|
|
259
|
+
parts.push(entry.timestamp.toISOString());
|
|
260
|
+
parts.push(LEVEL_PREFIX[entry.level]);
|
|
261
|
+
if (entry.scope) {
|
|
262
|
+
parts.push(`[${entry.scope}]`);
|
|
263
|
+
}
|
|
264
|
+
parts.push(entry.message);
|
|
265
|
+
if (entry.context && Object.keys(entry.context).length > 0) {
|
|
266
|
+
parts.push(formatContext2(entry.context));
|
|
267
|
+
}
|
|
268
|
+
console.log(parts.join(" "));
|
|
269
|
+
};
|
|
270
|
+
// ../../internal/logger/src/formatters/production.ts
|
|
271
|
+
var productionFormatter = (entry) => {
|
|
272
|
+
const output = {
|
|
273
|
+
timestamp: entry.timestamp.toISOString(),
|
|
274
|
+
level: entry.level,
|
|
275
|
+
...entry.scope && { scope: entry.scope },
|
|
276
|
+
msg: entry.message
|
|
277
|
+
};
|
|
278
|
+
if (entry.context && Object.keys(entry.context).length > 0) {
|
|
279
|
+
Object.assign(output, entry.context);
|
|
280
|
+
}
|
|
281
|
+
console.log(JSON.stringify(output));
|
|
282
|
+
};
|
|
283
|
+
// ../../internal/logger/src/formatters/browser.ts
|
|
284
|
+
var LEVEL_STYLES = {
|
|
285
|
+
debug: "color: gray",
|
|
286
|
+
info: "color: #0ea5e9",
|
|
287
|
+
warn: "color: #f59e0b",
|
|
288
|
+
error: "color: #ef4444; font-weight: bold"
|
|
289
|
+
};
|
|
290
|
+
var LEVEL_METHODS = {
|
|
291
|
+
debug: "log",
|
|
292
|
+
info: "info",
|
|
293
|
+
warn: "warn",
|
|
294
|
+
error: "error"
|
|
295
|
+
};
|
|
296
|
+
var browserFormatter = (entry) => {
|
|
297
|
+
const method = LEVEL_METHODS[entry.level];
|
|
298
|
+
const style = LEVEL_STYLES[entry.level];
|
|
299
|
+
const prefix = entry.scope ? `[${entry.scope}]` : "";
|
|
300
|
+
const label = `%c${prefix} ${entry.message}`;
|
|
301
|
+
if (entry.context && Object.keys(entry.context).length > 0) {
|
|
302
|
+
console[method](label, style, entry.context);
|
|
303
|
+
} else {
|
|
304
|
+
console[method](label, style);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
// ../../internal/logger/src/logger.ts
|
|
308
|
+
var LOG_LEVELS = ["debug", "info", "warn", "error"];
|
|
309
|
+
function getFormatter(env) {
|
|
310
|
+
switch (env) {
|
|
311
|
+
case "terminal":
|
|
312
|
+
return terminalFormatter;
|
|
313
|
+
case "ci":
|
|
314
|
+
return ciFormatter;
|
|
315
|
+
case "production":
|
|
316
|
+
return productionFormatter;
|
|
317
|
+
case "browser":
|
|
318
|
+
return browserFormatter;
|
|
319
|
+
case "test":
|
|
320
|
+
return () => {};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function getDefaultMinLevel() {
|
|
324
|
+
if (typeof process !== "undefined" && process.env?.DEBUG) {
|
|
325
|
+
return "debug";
|
|
326
|
+
}
|
|
327
|
+
return "info";
|
|
328
|
+
}
|
|
329
|
+
function shouldLog(level, minLevel) {
|
|
330
|
+
return LOG_LEVELS.indexOf(level) >= LOG_LEVELS.indexOf(minLevel);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
class Logger {
|
|
334
|
+
scope;
|
|
335
|
+
minLevel;
|
|
336
|
+
environment;
|
|
337
|
+
formatter;
|
|
338
|
+
defaultContext;
|
|
339
|
+
constructor(options = {}) {
|
|
340
|
+
this.scope = options.scope;
|
|
341
|
+
this.minLevel = options.minLevel ?? getDefaultMinLevel();
|
|
342
|
+
this.defaultContext = options.defaultContext ?? {};
|
|
343
|
+
this.environment = options.environment ?? detectEnvironment();
|
|
344
|
+
this.formatter = getFormatter(this.environment);
|
|
345
|
+
}
|
|
346
|
+
child(scope) {
|
|
347
|
+
const childScope = this.scope ? `${this.scope}:${scope}` : scope;
|
|
348
|
+
return new Logger({
|
|
349
|
+
scope: childScope,
|
|
350
|
+
minLevel: this.minLevel,
|
|
351
|
+
environment: this.environment,
|
|
352
|
+
defaultContext: { ...this.defaultContext }
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
withContext(context) {
|
|
356
|
+
return new Logger({
|
|
357
|
+
scope: this.scope,
|
|
358
|
+
minLevel: this.minLevel,
|
|
359
|
+
environment: this.environment,
|
|
360
|
+
defaultContext: { ...this.defaultContext, ...context }
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
debug(message, context) {
|
|
364
|
+
this.log("debug", message, context);
|
|
365
|
+
}
|
|
366
|
+
info(message, context) {
|
|
367
|
+
this.log("info", message, context);
|
|
368
|
+
}
|
|
369
|
+
warn(message, context) {
|
|
370
|
+
this.log("warn", message, context);
|
|
371
|
+
}
|
|
372
|
+
error(message, context) {
|
|
373
|
+
this.log("error", message, context);
|
|
374
|
+
}
|
|
375
|
+
log(level, message, context) {
|
|
376
|
+
if (level === "debug" && !shouldShowDebug(this.scope)) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (!shouldLog(level, this.minLevel)) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const entry = {
|
|
383
|
+
level,
|
|
384
|
+
message,
|
|
385
|
+
scope: this.scope,
|
|
386
|
+
context: context || Object.keys(this.defaultContext).length > 0 ? { ...this.defaultContext, ...context } : undefined,
|
|
387
|
+
timestamp: new Date
|
|
388
|
+
};
|
|
389
|
+
this.formatter(entry);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function createLogger(options = {}) {
|
|
393
|
+
return new Logger(options);
|
|
394
|
+
}
|
|
395
|
+
// ../../internal/client-infra/src/auth/token-manager.ts
|
|
396
|
+
function isDebug() {
|
|
397
|
+
try {
|
|
398
|
+
const debug = typeof process === "undefined" ? undefined : process.env["DEBUG"];
|
|
399
|
+
return debug === "1" || debug === "true";
|
|
400
|
+
} catch {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
var log = createLogger({ scope: "auth", minLevel: isDebug() ? "debug" : "warn" });
|
|
405
|
+
|
|
406
|
+
class TokenManager {
|
|
407
|
+
config;
|
|
408
|
+
accessToken = null;
|
|
409
|
+
tokenExpiry = 0;
|
|
410
|
+
pendingRequest = null;
|
|
411
|
+
fetchFn;
|
|
412
|
+
constructor(config) {
|
|
413
|
+
this.config = config;
|
|
414
|
+
this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
415
|
+
}
|
|
416
|
+
async getToken() {
|
|
417
|
+
if (this.accessToken && Date.now() < this.tokenExpiry) {
|
|
418
|
+
log.debug("Using cached token");
|
|
419
|
+
return this.accessToken;
|
|
420
|
+
}
|
|
421
|
+
if (this.pendingRequest) {
|
|
422
|
+
log.debug("Waiting for in-flight token request");
|
|
423
|
+
return this.pendingRequest;
|
|
424
|
+
}
|
|
425
|
+
this.pendingRequest = this.fetchToken();
|
|
426
|
+
try {
|
|
427
|
+
return await this.pendingRequest;
|
|
428
|
+
} finally {
|
|
429
|
+
this.pendingRequest = null;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
async fetchToken() {
|
|
433
|
+
log.debug("Fetching new access token...");
|
|
434
|
+
const { clientId, clientSecret } = this.config.credentials;
|
|
435
|
+
const credentials = btoa(`${clientId}:${clientSecret}`);
|
|
436
|
+
const start = performance.now();
|
|
437
|
+
const response = await this.fetchFn(this.config.tokenUrl, {
|
|
438
|
+
method: "POST",
|
|
439
|
+
headers: {
|
|
440
|
+
Authorization: `Basic ${credentials}`,
|
|
441
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
442
|
+
},
|
|
443
|
+
body: "grant_type=client_credentials"
|
|
444
|
+
});
|
|
445
|
+
const duration = Math.round(performance.now() - start);
|
|
446
|
+
if (!response.ok) {
|
|
447
|
+
log.error(`Token request failed: ${response.status} ${response.statusText}`);
|
|
448
|
+
throw new Error(`Failed to obtain access token: ${response.status} ${response.statusText}`);
|
|
449
|
+
}
|
|
450
|
+
const data = await response.json();
|
|
451
|
+
this.accessToken = data.access_token;
|
|
452
|
+
this.tokenExpiry = Date.now() + (data.expires_in - 60) * 1000;
|
|
453
|
+
log.debug(`Token acquired (${duration}ms, expires in ${data.expires_in}s)`);
|
|
454
|
+
return this.accessToken;
|
|
455
|
+
}
|
|
456
|
+
invalidate() {
|
|
457
|
+
log.debug("Token invalidated");
|
|
458
|
+
this.accessToken = null;
|
|
459
|
+
this.tokenExpiry = 0;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
// ../../internal/client-infra/src/config/paths.ts
|
|
463
|
+
var BEYONDAI_PATHS = {
|
|
464
|
+
caliper: {
|
|
465
|
+
send: "/caliper/event",
|
|
466
|
+
validate: "/caliper/event/validate",
|
|
467
|
+
list: "/caliper/events",
|
|
468
|
+
get: "/caliper/events/{id}",
|
|
469
|
+
jobStatus: "/jobs/{id}/status"
|
|
470
|
+
},
|
|
471
|
+
oneroster: {
|
|
472
|
+
rostering: "/ims/oneroster/rostering/v1p2",
|
|
473
|
+
gradebook: "/ims/oneroster/gradebook/v1p2",
|
|
474
|
+
resources: "/ims/oneroster/resources/v1p2"
|
|
475
|
+
},
|
|
476
|
+
edubridge: {
|
|
477
|
+
base: "/edubridge"
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
var LEARNWITHAI_PATHS = {
|
|
481
|
+
caliper: {
|
|
482
|
+
send: "/events/1.0/",
|
|
483
|
+
validate: null,
|
|
484
|
+
list: null,
|
|
485
|
+
get: null,
|
|
486
|
+
jobStatus: null
|
|
487
|
+
},
|
|
488
|
+
oneroster: {
|
|
489
|
+
rostering: "/rostering/1.0",
|
|
490
|
+
gradebook: "/gradebook/1.0",
|
|
491
|
+
resources: "/resources/1.0"
|
|
492
|
+
},
|
|
493
|
+
edubridge: null
|
|
494
|
+
};
|
|
495
|
+
var PLATFORM_PATHS = {
|
|
496
|
+
BEYOND_AI: BEYONDAI_PATHS,
|
|
497
|
+
LEARNWITH_AI: LEARNWITHAI_PATHS
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
// ../../internal/client-infra/src/config/provider.ts
|
|
501
|
+
function isEnvConfig(config) {
|
|
502
|
+
return "env" in config && !("baseUrl" in config) && !("services" in config);
|
|
503
|
+
}
|
|
504
|
+
function isExplicitConfig(config) {
|
|
505
|
+
return "baseUrl" in config && !("services" in config);
|
|
506
|
+
}
|
|
507
|
+
function isServicesConfig(config) {
|
|
508
|
+
return "services" in config;
|
|
509
|
+
}
|
|
510
|
+
function resolvePathProfiles(pathProfile, customPaths) {
|
|
511
|
+
const basePaths = pathProfile ? PLATFORM_PATHS[pathProfile] ?? BEYONDAI_PATHS : BEYONDAI_PATHS;
|
|
512
|
+
return {
|
|
513
|
+
caliper: customPaths?.caliper ?? basePaths.caliper,
|
|
514
|
+
oneroster: customPaths?.oneroster ?? basePaths.oneroster,
|
|
515
|
+
edubridge: customPaths?.edubridge ?? basePaths.edubridge
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
class TimebackProvider {
|
|
520
|
+
platform;
|
|
521
|
+
env;
|
|
522
|
+
auth;
|
|
523
|
+
timeout;
|
|
524
|
+
endpoints;
|
|
525
|
+
authUrl;
|
|
526
|
+
pathProfiles;
|
|
527
|
+
tokenManagers = new Map;
|
|
528
|
+
constructor(config) {
|
|
529
|
+
this.timeout = config.timeout ?? 30000;
|
|
530
|
+
if (isEnvConfig(config)) {
|
|
531
|
+
this.auth = config.auth;
|
|
532
|
+
const platform = config.platform ?? DEFAULT_PLATFORM;
|
|
533
|
+
const env = config.env;
|
|
534
|
+
this.platform = platform;
|
|
535
|
+
this.env = env;
|
|
536
|
+
const platformEndpoints = PLATFORM_ENDPOINTS[platform];
|
|
537
|
+
if (!platformEndpoints) {
|
|
538
|
+
throw new Error(`Unknown platform: ${platform}`);
|
|
539
|
+
}
|
|
540
|
+
this.authUrl = platformEndpoints.token[env];
|
|
541
|
+
this.pathProfiles = PLATFORM_PATHS[platform] ?? BEYONDAI_PATHS;
|
|
542
|
+
this.endpoints = {
|
|
543
|
+
oneroster: {
|
|
544
|
+
baseUrl: platformEndpoints.api[env],
|
|
545
|
+
authUrl: this.authUrl
|
|
546
|
+
},
|
|
547
|
+
edubridge: {
|
|
548
|
+
baseUrl: platformEndpoints.api[env],
|
|
549
|
+
authUrl: this.authUrl
|
|
550
|
+
},
|
|
551
|
+
caliper: {
|
|
552
|
+
baseUrl: platformEndpoints.caliper[env],
|
|
553
|
+
authUrl: this.authUrl
|
|
554
|
+
},
|
|
555
|
+
qti: {
|
|
556
|
+
baseUrl: platformEndpoints.qti[env],
|
|
557
|
+
authUrl: this.authUrl
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
} else if (isExplicitConfig(config)) {
|
|
561
|
+
this.auth = config.auth;
|
|
562
|
+
this.authUrl = config.authUrl;
|
|
563
|
+
this.pathProfiles = resolvePathProfiles(config.pathProfile, config.paths);
|
|
564
|
+
this.endpoints = {
|
|
565
|
+
oneroster: { baseUrl: config.baseUrl, authUrl: this.authUrl },
|
|
566
|
+
edubridge: { baseUrl: config.baseUrl, authUrl: this.authUrl },
|
|
567
|
+
caliper: { baseUrl: config.baseUrl, authUrl: this.authUrl },
|
|
568
|
+
qti: { baseUrl: config.baseUrl, authUrl: this.authUrl }
|
|
569
|
+
};
|
|
570
|
+
} else if (isServicesConfig(config)) {
|
|
571
|
+
this.auth = config.auth;
|
|
572
|
+
this.authUrl = config.authUrl;
|
|
573
|
+
this.pathProfiles = resolvePathProfiles(config.pathProfile, config.paths);
|
|
574
|
+
this.endpoints = {};
|
|
575
|
+
for (const [service, baseUrl] of Object.entries(config.services)) {
|
|
576
|
+
if (baseUrl) {
|
|
577
|
+
this.endpoints[service] = {
|
|
578
|
+
baseUrl,
|
|
579
|
+
authUrl: this.authUrl
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
} else {
|
|
584
|
+
throw new Error("Invalid provider configuration");
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
getEndpoint(service) {
|
|
588
|
+
const endpoint = this.endpoints[service];
|
|
589
|
+
if (!endpoint) {
|
|
590
|
+
throw new Error(`Service "${service}" is not configured in this provider`);
|
|
591
|
+
}
|
|
592
|
+
return endpoint;
|
|
593
|
+
}
|
|
594
|
+
hasService(service) {
|
|
595
|
+
return service in this.endpoints;
|
|
596
|
+
}
|
|
597
|
+
getAvailableServices() {
|
|
598
|
+
return Object.keys(this.endpoints);
|
|
599
|
+
}
|
|
600
|
+
getTokenUrl() {
|
|
601
|
+
return this.authUrl;
|
|
602
|
+
}
|
|
603
|
+
getEndpointWithPaths(service) {
|
|
604
|
+
const endpoint = this.getEndpoint(service);
|
|
605
|
+
const paths = this.getServicePaths(service);
|
|
606
|
+
return { ...endpoint, paths };
|
|
607
|
+
}
|
|
608
|
+
getPaths() {
|
|
609
|
+
return this.pathProfiles;
|
|
610
|
+
}
|
|
611
|
+
getServicePaths(service) {
|
|
612
|
+
const paths = this.pathProfiles[service];
|
|
613
|
+
if (!paths) {
|
|
614
|
+
throw new Error(`Service "${service}" is not supported on ${this.platform ?? "this"} platform.`);
|
|
615
|
+
}
|
|
616
|
+
return paths;
|
|
617
|
+
}
|
|
618
|
+
hasServiceSupport(service) {
|
|
619
|
+
return this.pathProfiles[service] !== null;
|
|
620
|
+
}
|
|
621
|
+
getTokenProvider(service) {
|
|
622
|
+
const endpoint = this.getEndpoint(service);
|
|
623
|
+
const { authUrl } = endpoint;
|
|
624
|
+
if (!authUrl) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (!this.auth) {
|
|
628
|
+
throw new Error(`Service "${service}" requires authentication but no credentials were provided`);
|
|
629
|
+
}
|
|
630
|
+
let manager = this.tokenManagers.get(authUrl);
|
|
631
|
+
if (!manager) {
|
|
632
|
+
manager = new TokenManager({
|
|
633
|
+
tokenUrl: authUrl,
|
|
634
|
+
credentials: {
|
|
635
|
+
clientId: this.auth.clientId,
|
|
636
|
+
clientSecret: this.auth.clientSecret
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
this.tokenManagers.set(authUrl, manager);
|
|
640
|
+
}
|
|
641
|
+
return manager;
|
|
642
|
+
}
|
|
643
|
+
async checkAuth() {
|
|
644
|
+
if (!this.authUrl || !this.auth) {
|
|
645
|
+
throw new Error("No auth configured on this provider");
|
|
646
|
+
}
|
|
647
|
+
const startTime = Date.now();
|
|
648
|
+
let manager = this.tokenManagers.get(this.authUrl);
|
|
649
|
+
if (!manager) {
|
|
650
|
+
manager = new TokenManager({
|
|
651
|
+
tokenUrl: this.authUrl,
|
|
652
|
+
credentials: {
|
|
653
|
+
clientId: this.auth.clientId,
|
|
654
|
+
clientSecret: this.auth.clientSecret
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
this.tokenManagers.set(this.authUrl, manager);
|
|
658
|
+
}
|
|
659
|
+
try {
|
|
660
|
+
await manager.getToken();
|
|
661
|
+
return {
|
|
662
|
+
ok: true,
|
|
663
|
+
latencyMs: Date.now() - startTime,
|
|
664
|
+
checks: { tokenAcquisition: true }
|
|
665
|
+
};
|
|
666
|
+
} catch (error) {
|
|
667
|
+
return {
|
|
668
|
+
ok: false,
|
|
669
|
+
latencyMs: Date.now() - startTime,
|
|
670
|
+
error: error instanceof Error ? error.message : String(error),
|
|
671
|
+
checks: { tokenAcquisition: false }
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
invalidateTokens() {
|
|
676
|
+
for (const manager of this.tokenManagers.values()) {
|
|
677
|
+
manager.invalidate?.();
|
|
678
|
+
}
|
|
679
|
+
this.tokenManagers.clear();
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
// ../../internal/client-infra/src/utils/utils.ts
|
|
683
|
+
function getEnv(key) {
|
|
684
|
+
try {
|
|
685
|
+
return typeof process === "undefined" ? undefined : process.env[key];
|
|
686
|
+
} catch {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
function isDebug2() {
|
|
691
|
+
const debug = getEnv("DEBUG");
|
|
692
|
+
return debug === "1" || debug === "true";
|
|
693
|
+
}
|
|
694
|
+
function generateRequestId() {
|
|
695
|
+
return Math.random().toString(16).slice(2, 10);
|
|
696
|
+
}
|
|
697
|
+
function createScopedLogger(scope) {
|
|
698
|
+
return createLogger({
|
|
699
|
+
scope,
|
|
700
|
+
minLevel: isDebug2() ? "debug" : "warn"
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
// ../../internal/client-infra/src/config/registry.ts
|
|
704
|
+
var DEFAULT_PROVIDER_REGISTRY = {
|
|
705
|
+
defaultPlatform: DEFAULT_PLATFORM,
|
|
706
|
+
templates: {
|
|
707
|
+
BEYOND_AI: {
|
|
708
|
+
staging: { platform: "BEYOND_AI", env: "staging" },
|
|
709
|
+
production: { platform: "BEYOND_AI", env: "production" }
|
|
710
|
+
},
|
|
711
|
+
LEARNWITH_AI: {
|
|
712
|
+
staging: { platform: "LEARNWITH_AI", env: "staging" },
|
|
713
|
+
production: { platform: "LEARNWITH_AI", env: "production" }
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
// ../../internal/client-infra/src/config/resolve.ts
|
|
719
|
+
function validateEnv(env) {
|
|
720
|
+
if (env !== "staging" && env !== "production") {
|
|
721
|
+
throw new Error(`Invalid env "${env}": must be "staging" or "production"`);
|
|
722
|
+
}
|
|
723
|
+
return env;
|
|
724
|
+
}
|
|
725
|
+
function validateAuth(auth, envVars) {
|
|
726
|
+
const clientId = auth?.clientId ?? getEnv(envVars.clientId);
|
|
727
|
+
const clientSecret = auth?.clientSecret ?? getEnv(envVars.clientSecret);
|
|
728
|
+
if (!clientId) {
|
|
729
|
+
throw new Error(`Missing clientId: provide in config or set ${envVars.clientId}`);
|
|
730
|
+
}
|
|
731
|
+
if (!clientSecret) {
|
|
732
|
+
throw new Error(`Missing clientSecret: provide in config or set ${envVars.clientSecret}`);
|
|
733
|
+
}
|
|
734
|
+
return { clientId, clientSecret };
|
|
735
|
+
}
|
|
736
|
+
function validateAuthOptional(auth, envVars, requireAuth) {
|
|
737
|
+
if (!requireAuth) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
return validateAuth(auth, envVars);
|
|
741
|
+
}
|
|
742
|
+
function buildMissingEnvError(envVars) {
|
|
743
|
+
const baseUrl = getEnv(envVars.baseUrl);
|
|
744
|
+
const authUrl = getEnv(envVars.authUrl);
|
|
745
|
+
const clientId = getEnv(envVars.clientId);
|
|
746
|
+
const clientSecret = getEnv(envVars.clientSecret);
|
|
747
|
+
if (baseUrl === undefined && clientId === undefined) {
|
|
748
|
+
const hint = envVars.env ?? envVars.baseUrl;
|
|
749
|
+
return `Missing env: provide in config or set ${hint}`;
|
|
750
|
+
}
|
|
751
|
+
const missing = [];
|
|
752
|
+
if (baseUrl === undefined) {
|
|
753
|
+
missing.push(envVars.env ?? envVars.baseUrl);
|
|
754
|
+
}
|
|
755
|
+
if (baseUrl !== undefined && authUrl === undefined) {
|
|
756
|
+
missing.push(envVars.authUrl);
|
|
757
|
+
}
|
|
758
|
+
if (clientId === undefined) {
|
|
759
|
+
missing.push(envVars.clientId);
|
|
760
|
+
}
|
|
761
|
+
if (clientSecret === undefined) {
|
|
762
|
+
missing.push(envVars.clientSecret);
|
|
763
|
+
}
|
|
764
|
+
return `Missing environment variables: ${missing.join(", ")}`;
|
|
765
|
+
}
|
|
766
|
+
var MODE_TRANSPORT = {
|
|
767
|
+
name: "transport",
|
|
768
|
+
matches(config) {
|
|
769
|
+
return "transport" in config && !!config.transport;
|
|
770
|
+
},
|
|
771
|
+
resolve(config) {
|
|
772
|
+
const c = config;
|
|
773
|
+
return { mode: "transport", transport: c.transport };
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
var MODE_PROVIDER = {
|
|
777
|
+
name: "provider",
|
|
778
|
+
matches(config) {
|
|
779
|
+
return "provider" in config && !!config.provider;
|
|
780
|
+
},
|
|
781
|
+
resolve(config) {
|
|
782
|
+
const c = config;
|
|
783
|
+
return { mode: "provider", provider: c.provider };
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
var MODE_ENV_CONFIG = {
|
|
787
|
+
name: "env-config",
|
|
788
|
+
matches(config) {
|
|
789
|
+
return "env" in config;
|
|
790
|
+
},
|
|
791
|
+
resolve(config, envVars, registry) {
|
|
792
|
+
const c = config;
|
|
793
|
+
const env = validateEnv(c.env);
|
|
794
|
+
const auth = validateAuth(c.auth, envVars);
|
|
795
|
+
const platform = c.platform ?? registry.defaultPlatform;
|
|
796
|
+
const platformTemplates = registry.templates[platform];
|
|
797
|
+
if (!platformTemplates) {
|
|
798
|
+
const available = Object.keys(registry.templates).join(", ");
|
|
799
|
+
throw new Error(`Unknown platform "${platform}": available platforms are ${available}`);
|
|
800
|
+
}
|
|
801
|
+
const template = platformTemplates[env];
|
|
802
|
+
if (!template) {
|
|
803
|
+
const available = Object.keys(platformTemplates).join(", ");
|
|
804
|
+
throw new Error(`Unknown env "${env}" for platform "${platform}": available environments are ${available}`);
|
|
805
|
+
}
|
|
806
|
+
return {
|
|
807
|
+
mode: "provider",
|
|
808
|
+
provider: new TimebackProvider({
|
|
809
|
+
platform: template.platform,
|
|
810
|
+
env: template.env,
|
|
811
|
+
auth,
|
|
812
|
+
timeout: c.timeout
|
|
813
|
+
})
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
var MODE_EXPLICIT_CONFIG = {
|
|
818
|
+
name: "explicit-config",
|
|
819
|
+
matches(config) {
|
|
820
|
+
return "baseUrl" in config;
|
|
821
|
+
},
|
|
822
|
+
resolve(config, envVars) {
|
|
823
|
+
const c = config;
|
|
824
|
+
const authUrl = c.authUrl ?? c.auth?.authUrl;
|
|
825
|
+
const requireAuth = !!authUrl;
|
|
826
|
+
const auth = validateAuthOptional(c.auth, envVars, requireAuth);
|
|
827
|
+
return {
|
|
828
|
+
mode: "provider",
|
|
829
|
+
provider: new TimebackProvider({
|
|
830
|
+
baseUrl: c.baseUrl,
|
|
831
|
+
authUrl,
|
|
832
|
+
auth,
|
|
833
|
+
timeout: c.timeout,
|
|
834
|
+
pathProfile: c.pathProfile,
|
|
835
|
+
paths: c.paths
|
|
836
|
+
})
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
var MODE_ENV_FALLBACK_PLATFORM = {
|
|
841
|
+
name: "env-fallback-platform",
|
|
842
|
+
matches(config, envVars) {
|
|
843
|
+
if (Object.keys(config).length > 0)
|
|
844
|
+
return false;
|
|
845
|
+
const env = envVars.env ? getEnv(envVars.env) : undefined;
|
|
846
|
+
const clientId = getEnv(envVars.clientId);
|
|
847
|
+
const clientSecret = getEnv(envVars.clientSecret);
|
|
848
|
+
return env !== undefined && clientId !== undefined && clientSecret !== undefined;
|
|
849
|
+
},
|
|
850
|
+
resolve(_config, envVars, registry) {
|
|
851
|
+
const env = validateEnv(getEnv(envVars.env));
|
|
852
|
+
const clientId = getEnv(envVars.clientId);
|
|
853
|
+
const clientSecret = getEnv(envVars.clientSecret);
|
|
854
|
+
const platform = registry.defaultPlatform;
|
|
855
|
+
const template = registry.templates[platform]?.[env];
|
|
856
|
+
if (!template) {
|
|
857
|
+
throw new Error(`Unknown env "${env}" for platform "${platform}"`);
|
|
858
|
+
}
|
|
859
|
+
return {
|
|
860
|
+
mode: "provider",
|
|
861
|
+
provider: new TimebackProvider({
|
|
862
|
+
platform: template.platform,
|
|
863
|
+
env: template.env,
|
|
864
|
+
auth: { clientId, clientSecret }
|
|
865
|
+
})
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
var MODE_ENV_FALLBACK_EXPLICIT = {
|
|
870
|
+
name: "env-fallback-explicit",
|
|
871
|
+
matches(config, envVars) {
|
|
872
|
+
if (Object.keys(config).length > 0)
|
|
873
|
+
return false;
|
|
874
|
+
const baseUrl = getEnv(envVars.baseUrl);
|
|
875
|
+
const authUrl = getEnv(envVars.authUrl);
|
|
876
|
+
const clientId = getEnv(envVars.clientId);
|
|
877
|
+
const clientSecret = getEnv(envVars.clientSecret);
|
|
878
|
+
return baseUrl !== undefined && authUrl !== undefined && clientId !== undefined && clientSecret !== undefined;
|
|
879
|
+
},
|
|
880
|
+
resolve(_config, envVars) {
|
|
881
|
+
const baseUrl = getEnv(envVars.baseUrl);
|
|
882
|
+
const authUrl = getEnv(envVars.authUrl);
|
|
883
|
+
const clientId = getEnv(envVars.clientId);
|
|
884
|
+
const clientSecret = getEnv(envVars.clientSecret);
|
|
885
|
+
return {
|
|
886
|
+
mode: "provider",
|
|
887
|
+
provider: new TimebackProvider({
|
|
888
|
+
baseUrl,
|
|
889
|
+
authUrl,
|
|
890
|
+
auth: { clientId, clientSecret }
|
|
891
|
+
})
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
var MODE_TOKEN_PROVIDER = {
|
|
896
|
+
name: "token-provider",
|
|
897
|
+
matches(config) {
|
|
898
|
+
return "tokenProvider" in config;
|
|
899
|
+
},
|
|
900
|
+
resolve() {
|
|
901
|
+
throw new Error("TokenProvider mode is not supported with provider pattern. " + "Use { provider: TimebackProvider } or { env, auth } instead.");
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
var MODES = [
|
|
905
|
+
MODE_TRANSPORT,
|
|
906
|
+
MODE_PROVIDER,
|
|
907
|
+
MODE_ENV_CONFIG,
|
|
908
|
+
MODE_TOKEN_PROVIDER,
|
|
909
|
+
MODE_EXPLICIT_CONFIG,
|
|
910
|
+
MODE_ENV_FALLBACK_PLATFORM,
|
|
911
|
+
MODE_ENV_FALLBACK_EXPLICIT
|
|
912
|
+
];
|
|
913
|
+
function resolveToProvider(config, envVars, registry = DEFAULT_PROVIDER_REGISTRY) {
|
|
914
|
+
for (const mode of MODES) {
|
|
915
|
+
if (mode.matches(config, envVars)) {
|
|
916
|
+
return mode.resolve(config, envVars, registry);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
throw new Error(buildMissingEnvError(envVars));
|
|
920
|
+
}
|
|
921
|
+
// ../../internal/client-infra/src/errors/errors.ts
|
|
922
|
+
class ApiError extends Error {
|
|
923
|
+
statusCode;
|
|
924
|
+
response;
|
|
925
|
+
name = "ApiError";
|
|
926
|
+
constructor(message, statusCode, response) {
|
|
927
|
+
super(message);
|
|
928
|
+
this.statusCode = statusCode;
|
|
929
|
+
this.response = response;
|
|
930
|
+
}
|
|
931
|
+
get minorCodes() {
|
|
932
|
+
const ims = this.response;
|
|
933
|
+
if (!ims?.imsx_CodeMinor?.imsx_codeMinorField) {
|
|
934
|
+
return [];
|
|
935
|
+
}
|
|
936
|
+
return ims.imsx_CodeMinor.imsx_codeMinorField.map((field) => ({
|
|
937
|
+
field: field.imsx_codeMinorFieldName,
|
|
938
|
+
value: field.imsx_codeMinorFieldValue
|
|
939
|
+
}));
|
|
940
|
+
}
|
|
941
|
+
get details() {
|
|
942
|
+
const ims = this.response;
|
|
943
|
+
return ims?.imsx_error_details ?? [];
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
class UnauthorizedError extends ApiError {
|
|
948
|
+
name = "UnauthorizedError";
|
|
949
|
+
constructor(message = "Unauthorized", response) {
|
|
950
|
+
super(message, 401, response);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
class ForbiddenError extends ApiError {
|
|
955
|
+
name = "ForbiddenError";
|
|
956
|
+
constructor(message = "Forbidden", response) {
|
|
957
|
+
super(message, 403, response);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
class NotFoundError extends ApiError {
|
|
962
|
+
name = "NotFoundError";
|
|
963
|
+
constructor(message = "Not Found", response) {
|
|
964
|
+
super(message, 404, response);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
class ValidationError extends ApiError {
|
|
969
|
+
name = "ValidationError";
|
|
970
|
+
constructor(message = "Validation Error", response) {
|
|
971
|
+
super(message, 422, response);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
// ../../internal/client-infra/src/transport/constants.ts
|
|
975
|
+
var MAX_RETRIES = 3;
|
|
976
|
+
var RETRY_STATUS_CODES = [429, 503];
|
|
977
|
+
var INITIAL_RETRY_DELAY_MS = 1000;
|
|
978
|
+
|
|
979
|
+
// ../../internal/client-infra/src/transport/transport.ts
|
|
980
|
+
class BaseTransport {
|
|
981
|
+
config;
|
|
982
|
+
log;
|
|
983
|
+
constructor(options) {
|
|
984
|
+
const { config, logger } = options;
|
|
985
|
+
const fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
986
|
+
let tokenProvider;
|
|
987
|
+
if ("tokenProvider" in config && config.tokenProvider) {
|
|
988
|
+
tokenProvider = config.tokenProvider;
|
|
989
|
+
} else if ("auth" in config && config.auth) {
|
|
990
|
+
tokenProvider = new TokenManager({
|
|
991
|
+
tokenUrl: config.auth.authUrl,
|
|
992
|
+
credentials: {
|
|
993
|
+
clientId: config.auth.clientId,
|
|
994
|
+
clientSecret: config.auth.clientSecret
|
|
995
|
+
},
|
|
996
|
+
fetch: fetchFn
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
this.config = {
|
|
1000
|
+
baseUrl: config.baseUrl,
|
|
1001
|
+
timeout: config.timeout ?? 30000,
|
|
1002
|
+
fetch: fetchFn,
|
|
1003
|
+
tokenProvider
|
|
1004
|
+
};
|
|
1005
|
+
this.log = logger.child("http");
|
|
1006
|
+
}
|
|
1007
|
+
get baseUrl() {
|
|
1008
|
+
return this.config.baseUrl;
|
|
1009
|
+
}
|
|
1010
|
+
async request(path, options = {}) {
|
|
1011
|
+
const response = await this.requestRaw(path, options);
|
|
1012
|
+
return this.handleResponse(response);
|
|
1013
|
+
}
|
|
1014
|
+
async requestRaw(path, options = {}) {
|
|
1015
|
+
const { method = "GET", params, body, headers = {} } = options;
|
|
1016
|
+
const url = this.buildUrl(path, params);
|
|
1017
|
+
const requestId = options.requestId ?? generateRequestId();
|
|
1018
|
+
const operationStart = Date.now();
|
|
1019
|
+
const operationDeadline = operationStart + this.config.timeout;
|
|
1020
|
+
for (let attempt = 0;attempt < MAX_RETRIES; attempt++) {
|
|
1021
|
+
const remainingTime = operationDeadline - Date.now();
|
|
1022
|
+
if (remainingTime <= 0) {
|
|
1023
|
+
this.log.error("Request timeout before attempt", isDebug2() ? { requestId, path } : { path });
|
|
1024
|
+
throw new ApiError("Request timeout", 408);
|
|
1025
|
+
}
|
|
1026
|
+
const token = await this.getAccessToken();
|
|
1027
|
+
const isLastAttempt = attempt === MAX_RETRIES - 1;
|
|
1028
|
+
const start = performance.now();
|
|
1029
|
+
this.log.debug(`→ ${method} ${url}`, {
|
|
1030
|
+
requestId,
|
|
1031
|
+
attempt: attempt > 0 ? attempt + 1 : undefined
|
|
1032
|
+
});
|
|
1033
|
+
const requestHeaders = {
|
|
1034
|
+
"Content-Type": "application/json",
|
|
1035
|
+
Accept: "application/json",
|
|
1036
|
+
"X-Request-ID": requestId,
|
|
1037
|
+
...headers
|
|
1038
|
+
};
|
|
1039
|
+
if (token) {
|
|
1040
|
+
requestHeaders.Authorization = `Bearer ${token}`;
|
|
1041
|
+
}
|
|
1042
|
+
const response = await this.config.fetch(url, {
|
|
1043
|
+
method,
|
|
1044
|
+
headers: requestHeaders,
|
|
1045
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
1046
|
+
signal: AbortSignal.timeout(Math.min(remainingTime, this.config.timeout))
|
|
1047
|
+
});
|
|
1048
|
+
const duration = Math.round(performance.now() - start);
|
|
1049
|
+
this.log.debug(`← ${response.status} ${response.statusText} (${duration}ms)`, {
|
|
1050
|
+
requestId
|
|
1051
|
+
});
|
|
1052
|
+
const shouldRetry = RETRY_STATUS_CODES.includes(response.status) && !isLastAttempt;
|
|
1053
|
+
if (shouldRetry) {
|
|
1054
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
1055
|
+
const delayMs = this.parseRetryAfter(retryAfter, attempt);
|
|
1056
|
+
const timeRemaining = operationDeadline - Date.now();
|
|
1057
|
+
if (delayMs >= timeRemaining) {
|
|
1058
|
+
this.log.error("Request timeout during retry backoff", isDebug2() ? { requestId, path } : { path });
|
|
1059
|
+
throw new ApiError("Request timeout", 408);
|
|
1060
|
+
}
|
|
1061
|
+
this.log.warn(`Retrying in ${delayMs}ms (attempt ${attempt + 1}/${MAX_RETRIES})`, {
|
|
1062
|
+
...isDebug2() && { requestId },
|
|
1063
|
+
status: response.status
|
|
1064
|
+
});
|
|
1065
|
+
await this.sleep(delayMs);
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
if (!response.ok) {
|
|
1069
|
+
return this.handleErrorResponse(response, requestId);
|
|
1070
|
+
}
|
|
1071
|
+
return response;
|
|
1072
|
+
}
|
|
1073
|
+
this.log.error("Max retries exceeded", isDebug2() ? { requestId, path } : { path });
|
|
1074
|
+
throw new ApiError("Max retries exceeded");
|
|
1075
|
+
}
|
|
1076
|
+
getAccessToken() {
|
|
1077
|
+
if (!this.config.tokenProvider) {
|
|
1078
|
+
return Promise.resolve(undefined);
|
|
1079
|
+
}
|
|
1080
|
+
return this.config.tokenProvider.getToken();
|
|
1081
|
+
}
|
|
1082
|
+
buildUrl(path, params) {
|
|
1083
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(path)) {
|
|
1084
|
+
throw new Error(`Absolute URLs are not allowed in path: ${path}. Use relative paths only.`);
|
|
1085
|
+
}
|
|
1086
|
+
const url = new URL(path, this.config.baseUrl);
|
|
1087
|
+
if (params) {
|
|
1088
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1089
|
+
if (value !== undefined) {
|
|
1090
|
+
url.searchParams.set(key, String(value));
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
return url.toString();
|
|
1095
|
+
}
|
|
1096
|
+
async handleResponse(response) {
|
|
1097
|
+
if (!response.ok) {
|
|
1098
|
+
await this.handleErrorResponse(response);
|
|
1099
|
+
}
|
|
1100
|
+
if (response.status === 204) {
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
const contentLength = response.headers.get("content-length");
|
|
1104
|
+
if (contentLength === "0") {
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
return this.parseJsonResponse(response);
|
|
1108
|
+
}
|
|
1109
|
+
async parseJsonResponse(response) {
|
|
1110
|
+
const text = await response.text();
|
|
1111
|
+
if (!text || text.trim() === "") {
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
try {
|
|
1115
|
+
return JSON.parse(text);
|
|
1116
|
+
} catch (error) {
|
|
1117
|
+
const preview = text.length > 200 ? text.slice(0, 200) + "..." : text;
|
|
1118
|
+
const parseError = error instanceof Error ? error.message : String(error);
|
|
1119
|
+
const url = response.url || "unknown";
|
|
1120
|
+
this.log.error("Failed to parse JSON response", {
|
|
1121
|
+
url,
|
|
1122
|
+
status: response.status,
|
|
1123
|
+
contentType: response.headers.get("content-type"),
|
|
1124
|
+
bodyPreview: preview,
|
|
1125
|
+
error: parseError
|
|
1126
|
+
});
|
|
1127
|
+
throw new ApiError(`Invalid JSON response from ${url}`, response.status, {
|
|
1128
|
+
parseError,
|
|
1129
|
+
body: preview
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
async handleErrorResponse(response, requestId) {
|
|
1134
|
+
let errorBody;
|
|
1135
|
+
const text = await response.text().catch(() => "");
|
|
1136
|
+
if (text) {
|
|
1137
|
+
try {
|
|
1138
|
+
errorBody = JSON.parse(text);
|
|
1139
|
+
} catch (parseError) {
|
|
1140
|
+
const parseWarning = parseError instanceof Error ? parseError.message : "Unknown parse error";
|
|
1141
|
+
errorBody = { rawBody: text.slice(0, 500), parseError: parseWarning };
|
|
1142
|
+
this.log.warn("Failed to parse error response as JSON", {
|
|
1143
|
+
...isDebug2() && { requestId },
|
|
1144
|
+
url: response.url,
|
|
1145
|
+
status: response.status,
|
|
1146
|
+
parseError: parseWarning,
|
|
1147
|
+
bodyPreview: text.slice(0, 200)
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
const message = this.extractErrorMessage(errorBody, response.statusText);
|
|
1152
|
+
if (response.status !== 404) {
|
|
1153
|
+
this.log.error(`Request failed: ${response.status} ${message}`, isDebug2() ? { requestId } : undefined);
|
|
1154
|
+
}
|
|
1155
|
+
switch (response.status) {
|
|
1156
|
+
case 401:
|
|
1157
|
+
this.config.tokenProvider?.invalidate?.();
|
|
1158
|
+
throw new UnauthorizedError(message, errorBody);
|
|
1159
|
+
case 403:
|
|
1160
|
+
throw new ForbiddenError(message, errorBody);
|
|
1161
|
+
case 404:
|
|
1162
|
+
throw new NotFoundError(message, errorBody);
|
|
1163
|
+
case 422:
|
|
1164
|
+
throw new ValidationError(message, errorBody);
|
|
1165
|
+
default:
|
|
1166
|
+
throw new ApiError(message, response.status, errorBody);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
extractErrorMessage(body, fallback) {
|
|
1170
|
+
if (typeof body === "object" && body !== null) {
|
|
1171
|
+
const obj = body;
|
|
1172
|
+
if (typeof obj.message === "string")
|
|
1173
|
+
return obj.message;
|
|
1174
|
+
if (typeof obj.error === "string")
|
|
1175
|
+
return obj.error;
|
|
1176
|
+
if (typeof obj.imsx_description === "string")
|
|
1177
|
+
return obj.imsx_description;
|
|
1178
|
+
}
|
|
1179
|
+
return fallback;
|
|
1180
|
+
}
|
|
1181
|
+
sleep(ms) {
|
|
1182
|
+
return new Promise((resolve) => {
|
|
1183
|
+
setTimeout(resolve, ms);
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
parseRetryAfter(retryAfter, attempt) {
|
|
1187
|
+
if (!retryAfter) {
|
|
1188
|
+
return INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt);
|
|
1189
|
+
}
|
|
1190
|
+
const seconds = parseInt(retryAfter, 10);
|
|
1191
|
+
if (!isNaN(seconds)) {
|
|
1192
|
+
return seconds * 1000;
|
|
1193
|
+
}
|
|
1194
|
+
const date = Date.parse(retryAfter);
|
|
1195
|
+
if (!isNaN(date)) {
|
|
1196
|
+
const delayMs = date - Date.now();
|
|
1197
|
+
return Math.max(0, delayMs);
|
|
1198
|
+
}
|
|
1199
|
+
return INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
// ../../internal/client-infra/src/filter/where/where.ts
|
|
1203
|
+
function escapeValue(value) {
|
|
1204
|
+
if (value instanceof Date) {
|
|
1205
|
+
return value.toISOString();
|
|
1206
|
+
}
|
|
1207
|
+
if (typeof value === "boolean") {
|
|
1208
|
+
return value ? "true" : "false";
|
|
1209
|
+
}
|
|
1210
|
+
if (typeof value === "number") {
|
|
1211
|
+
return String(value);
|
|
1212
|
+
}
|
|
1213
|
+
return value.replaceAll("'", "''");
|
|
1214
|
+
}
|
|
1215
|
+
function formatValue2(value) {
|
|
1216
|
+
const escaped = escapeValue(value);
|
|
1217
|
+
const needsQuotes = typeof value === "string" || value instanceof Date;
|
|
1218
|
+
return needsQuotes ? `'${escaped}'` : escaped;
|
|
1219
|
+
}
|
|
1220
|
+
function fieldToConditions(field, condition) {
|
|
1221
|
+
if (typeof condition === "string" || typeof condition === "number" || typeof condition === "boolean" || condition instanceof Date) {
|
|
1222
|
+
return [`${field}=${formatValue2(condition)}`];
|
|
1223
|
+
}
|
|
1224
|
+
if (typeof condition === "object" && condition !== null) {
|
|
1225
|
+
const ops = condition;
|
|
1226
|
+
const conditions = [];
|
|
1227
|
+
if (ops.ne !== undefined) {
|
|
1228
|
+
conditions.push(`${field}!=${formatValue2(ops.ne)}`);
|
|
1229
|
+
}
|
|
1230
|
+
if (ops.gt !== undefined) {
|
|
1231
|
+
conditions.push(`${field}>${formatValue2(ops.gt)}`);
|
|
1232
|
+
}
|
|
1233
|
+
if (ops.gte !== undefined) {
|
|
1234
|
+
conditions.push(`${field}>=${formatValue2(ops.gte)}`);
|
|
1235
|
+
}
|
|
1236
|
+
if (ops.lt !== undefined) {
|
|
1237
|
+
conditions.push(`${field}<${formatValue2(ops.lt)}`);
|
|
1238
|
+
}
|
|
1239
|
+
if (ops.lte !== undefined) {
|
|
1240
|
+
conditions.push(`${field}<=${formatValue2(ops.lte)}`);
|
|
1241
|
+
}
|
|
1242
|
+
if (ops.contains !== undefined) {
|
|
1243
|
+
conditions.push(`${field}~${formatValue2(ops.contains)}`);
|
|
1244
|
+
}
|
|
1245
|
+
if (ops.in !== undefined && ops.in.length > 0) {
|
|
1246
|
+
const inConditions = ops.in.map((v) => `${field}=${formatValue2(v)}`);
|
|
1247
|
+
const joined = inConditions.join(" OR ");
|
|
1248
|
+
conditions.push(inConditions.length > 1 ? `(${joined})` : joined);
|
|
1249
|
+
}
|
|
1250
|
+
if (ops.notIn !== undefined && ops.notIn.length > 0) {
|
|
1251
|
+
const notInConditions = ops.notIn.map((v) => `${field}!=${formatValue2(v)}`);
|
|
1252
|
+
conditions.push(notInConditions.join(" AND "));
|
|
1253
|
+
}
|
|
1254
|
+
return conditions;
|
|
1255
|
+
}
|
|
1256
|
+
return [];
|
|
1257
|
+
}
|
|
1258
|
+
function isOrCondition(clause) {
|
|
1259
|
+
return "OR" in clause && Array.isArray(clause.OR);
|
|
1260
|
+
}
|
|
1261
|
+
function whereToFilter(where) {
|
|
1262
|
+
if (isOrCondition(where)) {
|
|
1263
|
+
const orParts = where.OR.map((clause) => whereToFilter(clause)).filter((s) => s !== undefined);
|
|
1264
|
+
return orParts.length > 0 ? orParts.join(" OR ") : undefined;
|
|
1265
|
+
}
|
|
1266
|
+
const conditions = [];
|
|
1267
|
+
for (const [field, condition] of Object.entries(where)) {
|
|
1268
|
+
if (condition !== undefined) {
|
|
1269
|
+
conditions.push(...fieldToConditions(field, condition));
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
return conditions.length > 0 ? conditions.join(" AND ") : undefined;
|
|
1273
|
+
}
|
|
1274
|
+
// ../../internal/client-infra/src/pagination/pagination.ts
|
|
1275
|
+
var DEFAULT_LIMIT = 100;
|
|
1276
|
+
var DEFAULT_MAX_ITEMS = 1e4;
|
|
1277
|
+
|
|
1278
|
+
class Paginator {
|
|
1279
|
+
fetcher;
|
|
1280
|
+
path;
|
|
1281
|
+
params;
|
|
1282
|
+
max;
|
|
1283
|
+
unwrapKey;
|
|
1284
|
+
log;
|
|
1285
|
+
transform;
|
|
1286
|
+
constructor(options) {
|
|
1287
|
+
this.fetcher = options.fetcher;
|
|
1288
|
+
this.path = options.path;
|
|
1289
|
+
this.params = options.params ?? {};
|
|
1290
|
+
this.max = options.max;
|
|
1291
|
+
this.unwrapKey = options.unwrapKey;
|
|
1292
|
+
this.log = options.logger?.child("pagination") ?? createScopedLogger("pagination");
|
|
1293
|
+
this.transform = options.transform;
|
|
1294
|
+
}
|
|
1295
|
+
buildRequestParams(limit, offset) {
|
|
1296
|
+
const { where, fields, ...rest } = this.params;
|
|
1297
|
+
return {
|
|
1298
|
+
...rest,
|
|
1299
|
+
filter: where ? whereToFilter(where) : undefined,
|
|
1300
|
+
fields: fields?.join(","),
|
|
1301
|
+
limit,
|
|
1302
|
+
offset
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
extractItems(data, pageNumber) {
|
|
1306
|
+
let items;
|
|
1307
|
+
if (this.unwrapKey) {
|
|
1308
|
+
const wrapped = data;
|
|
1309
|
+
items = wrapped[this.unwrapKey] ?? [];
|
|
1310
|
+
} else {
|
|
1311
|
+
items = data;
|
|
1312
|
+
}
|
|
1313
|
+
return this.validateItems(items, pageNumber);
|
|
1314
|
+
}
|
|
1315
|
+
validateItems(data, pageNumber) {
|
|
1316
|
+
if (data === null || data === undefined) {
|
|
1317
|
+
this.log.warn(`Page ${pageNumber}: response data is ${data}, treating as empty`);
|
|
1318
|
+
return [];
|
|
1319
|
+
}
|
|
1320
|
+
if (!Array.isArray(data)) {
|
|
1321
|
+
const type = typeof data;
|
|
1322
|
+
throw new Error(`Expected array for page ${pageNumber}, got ${type}. ` + (this.unwrapKey ? `Check unwrapKey '${this.unwrapKey}' is correct.` : ""));
|
|
1323
|
+
}
|
|
1324
|
+
return data;
|
|
1325
|
+
}
|
|
1326
|
+
hasMorePages(response, itemCount, offset, limit) {
|
|
1327
|
+
if (itemCount === 0) {
|
|
1328
|
+
return false;
|
|
1329
|
+
}
|
|
1330
|
+
const hasMoreFromLink = response.hasMore;
|
|
1331
|
+
const hasMoreFromTotal = response.total !== undefined && offset + itemCount < response.total;
|
|
1332
|
+
const hasMoreFromPageSize = itemCount === limit;
|
|
1333
|
+
return hasMoreFromLink || hasMoreFromTotal || hasMoreFromPageSize && response.total === undefined;
|
|
1334
|
+
}
|
|
1335
|
+
async* [Symbol.asyncIterator]() {
|
|
1336
|
+
let offset = this.params.offset ?? 0;
|
|
1337
|
+
const limit = this.params.limit ?? DEFAULT_LIMIT;
|
|
1338
|
+
let hasMore = true;
|
|
1339
|
+
let pageNumber = 1;
|
|
1340
|
+
let totalYielded = 0;
|
|
1341
|
+
while (hasMore) {
|
|
1342
|
+
this.log.debug(`Fetching page ${pageNumber} (offset: ${offset}, limit: ${limit})`);
|
|
1343
|
+
const response = await this.fetcher(this.path, {
|
|
1344
|
+
params: this.buildRequestParams(limit, offset)
|
|
1345
|
+
});
|
|
1346
|
+
const items = this.extractItems(response.data, pageNumber);
|
|
1347
|
+
this.log.debug(`Page ${pageNumber}: ${items.length} items`);
|
|
1348
|
+
for (const item of items) {
|
|
1349
|
+
yield this.transform ? this.transform(item) : item;
|
|
1350
|
+
totalYielded++;
|
|
1351
|
+
if (this.max !== undefined && totalYielded >= this.max) {
|
|
1352
|
+
this.log.debug(`Pagination complete: reached max of ${this.max} items`);
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
hasMore = this.hasMorePages(response, items.length, offset, limit);
|
|
1357
|
+
offset += items.length;
|
|
1358
|
+
pageNumber++;
|
|
1359
|
+
}
|
|
1360
|
+
this.log.debug(`Pagination complete: ${totalYielded} total items`);
|
|
1361
|
+
}
|
|
1362
|
+
async toArray(options = {}) {
|
|
1363
|
+
const maxItems = options.maxItems ?? DEFAULT_MAX_ITEMS;
|
|
1364
|
+
const items = [];
|
|
1365
|
+
for await (const item of this) {
|
|
1366
|
+
if (items.length >= maxItems) {
|
|
1367
|
+
throw new Error(`toArray() exceeded maxItems limit of ${maxItems}. ` + `Use 'for await...of' to stream large datasets, or increase maxItems.`);
|
|
1368
|
+
}
|
|
1369
|
+
items.push(item);
|
|
1370
|
+
}
|
|
1371
|
+
return items;
|
|
1372
|
+
}
|
|
1373
|
+
async firstPage() {
|
|
1374
|
+
const limit = this.params.limit ?? DEFAULT_LIMIT;
|
|
1375
|
+
const offset = this.params.offset ?? 0;
|
|
1376
|
+
const response = await this.fetcher(this.path, {
|
|
1377
|
+
params: this.buildRequestParams(limit, offset)
|
|
1378
|
+
});
|
|
1379
|
+
const rawItems = this.extractItems(response.data, 1);
|
|
1380
|
+
const items = this.transform ? rawItems.map(this.transform) : rawItems;
|
|
1381
|
+
const hasMore = this.hasMorePages(response, items.length, offset, limit);
|
|
1382
|
+
this.log.debug(`First page: ${items.length} items, total: ${response.total}, hasMore: ${hasMore}`);
|
|
1383
|
+
return {
|
|
1384
|
+
data: items,
|
|
1385
|
+
hasMore,
|
|
1386
|
+
total: response.total,
|
|
1387
|
+
nextOffset: hasMore ? offset + items.length : undefined
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
// ../../internal/client-infra/src/pagination/strategies.ts
|
|
1392
|
+
function hasNextLink(linkHeader) {
|
|
1393
|
+
if (!linkHeader)
|
|
1394
|
+
return false;
|
|
1395
|
+
return /rel=["']?next["']?(?:\s|;|,|$)/i.test(linkHeader);
|
|
1396
|
+
}
|
|
1397
|
+
function parseHeaderPagination(response, data) {
|
|
1398
|
+
const linkHeader = response.headers.get("Link");
|
|
1399
|
+
const totalHeader = response.headers.get("X-Total-Count");
|
|
1400
|
+
const parsedTotal = totalHeader ? parseInt(totalHeader, 10) : undefined;
|
|
1401
|
+
const total = parsedTotal !== undefined && !Number.isNaN(parsedTotal) ? parsedTotal : undefined;
|
|
1402
|
+
return {
|
|
1403
|
+
data,
|
|
1404
|
+
hasMore: hasNextLink(linkHeader),
|
|
1405
|
+
total
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
// src/constants.ts
|
|
1409
|
+
var ONEROSTER_ENV_VARS = {
|
|
1410
|
+
baseUrl: "ONEROSTER_BASE_URL",
|
|
1411
|
+
clientId: "ONEROSTER_CLIENT_ID",
|
|
1412
|
+
clientSecret: "ONEROSTER_CLIENT_SECRET",
|
|
1413
|
+
authUrl: "ONEROSTER_TOKEN_URL"
|
|
1414
|
+
};
|
|
1415
|
+
|
|
1416
|
+
// src/lib/resolve.ts
|
|
1417
|
+
function resolveToProvider2(config, registry = DEFAULT_PROVIDER_REGISTRY) {
|
|
1418
|
+
return resolveToProvider(config, ONEROSTER_ENV_VARS, registry);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// src/utils.ts
|
|
1422
|
+
var log2 = createScopedLogger("oneroster");
|
|
1423
|
+
function parseGrades(grades) {
|
|
1424
|
+
if (!grades)
|
|
1425
|
+
return;
|
|
1426
|
+
return grades.map((g) => Number(g));
|
|
1427
|
+
}
|
|
1428
|
+
function normalizeDateOnly(date) {
|
|
1429
|
+
return date.slice(0, 10);
|
|
1430
|
+
}
|
|
1431
|
+
function normalizeBoolean(value) {
|
|
1432
|
+
if (typeof value === "boolean")
|
|
1433
|
+
return value;
|
|
1434
|
+
return value === "true";
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
// src/lib/transport.ts
|
|
1438
|
+
class Transport extends BaseTransport {
|
|
1439
|
+
paths;
|
|
1440
|
+
constructor(config) {
|
|
1441
|
+
super({ config, logger: log2 });
|
|
1442
|
+
this.paths = config.paths;
|
|
1443
|
+
}
|
|
1444
|
+
async requestPaginated(path, options = {}) {
|
|
1445
|
+
const response = await this.requestRaw(path, options);
|
|
1446
|
+
const data = await response.json();
|
|
1447
|
+
return parseHeaderPagination(response, data);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
// src/lib/pagination.ts
|
|
1451
|
+
class Paginator2 extends Paginator {
|
|
1452
|
+
constructor(transport, path, params = {}, unwrapKey, transform) {
|
|
1453
|
+
const { max, ...requestParams } = params;
|
|
1454
|
+
super({
|
|
1455
|
+
fetcher: (p, opts) => transport.requestPaginated(p, opts),
|
|
1456
|
+
path,
|
|
1457
|
+
params: requestParams,
|
|
1458
|
+
max,
|
|
1459
|
+
unwrapKey,
|
|
1460
|
+
logger: log2,
|
|
1461
|
+
transform
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
// src/resources/base.ts
|
|
1466
|
+
class BaseResource {
|
|
1467
|
+
transport;
|
|
1468
|
+
basePath;
|
|
1469
|
+
constructor(transport, resourceType, suffix) {
|
|
1470
|
+
this.transport = transport;
|
|
1471
|
+
const prefix = transport.paths[resourceType];
|
|
1472
|
+
this.basePath = `${prefix}${suffix}`;
|
|
1473
|
+
}
|
|
1474
|
+
list(params) {
|
|
1475
|
+
return this.stream(params).toArray();
|
|
1476
|
+
}
|
|
1477
|
+
stream(params) {
|
|
1478
|
+
return new Paginator2(this.transport, this.basePath, params, this.unwrapKey, this.transform.bind(this));
|
|
1479
|
+
}
|
|
1480
|
+
async get(sourcedId) {
|
|
1481
|
+
const response = await this.transport.request(`${this.basePath}/${sourcedId}`);
|
|
1482
|
+
return this.unwrapSingle(response);
|
|
1483
|
+
}
|
|
1484
|
+
create(data) {
|
|
1485
|
+
const body = this.wrapBody(data);
|
|
1486
|
+
return this.transport.request(this.basePath, {
|
|
1487
|
+
method: "POST",
|
|
1488
|
+
body
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
async update(sourcedId, data) {
|
|
1492
|
+
const body = this.wrapBody(data);
|
|
1493
|
+
await this.transport.request(`${this.basePath}/${sourcedId}`, {
|
|
1494
|
+
method: "PUT",
|
|
1495
|
+
body
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
async delete(sourcedId) {
|
|
1499
|
+
await this.transport.request(`${this.basePath}/${sourcedId}`, {
|
|
1500
|
+
method: "DELETE"
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
transform(entity) {
|
|
1504
|
+
return entity;
|
|
1505
|
+
}
|
|
1506
|
+
unwrapSingle(response) {
|
|
1507
|
+
const singularKey = this.wrapKey;
|
|
1508
|
+
const data = response[singularKey];
|
|
1509
|
+
if (!data) {
|
|
1510
|
+
throw new Error(`Expected "${singularKey}" in response`);
|
|
1511
|
+
}
|
|
1512
|
+
return this.transform(data);
|
|
1513
|
+
}
|
|
1514
|
+
wrapBody(data) {
|
|
1515
|
+
return { [this.wrapKey]: data };
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
class ReadOnlyResource {
|
|
1520
|
+
transport;
|
|
1521
|
+
basePath;
|
|
1522
|
+
constructor(transport, resourceType, suffix) {
|
|
1523
|
+
this.transport = transport;
|
|
1524
|
+
const prefix = transport.paths[resourceType];
|
|
1525
|
+
this.basePath = `${prefix}${suffix}`;
|
|
1526
|
+
}
|
|
1527
|
+
list(params) {
|
|
1528
|
+
return this.stream(params).toArray();
|
|
1529
|
+
}
|
|
1530
|
+
stream(params) {
|
|
1531
|
+
return new Paginator2(this.transport, this.basePath, params, this.unwrapKey, this.transform.bind(this));
|
|
1532
|
+
}
|
|
1533
|
+
async get(sourcedId) {
|
|
1534
|
+
const response = await this.transport.request(`${this.basePath}/${sourcedId}`);
|
|
1535
|
+
const data = response[this.wrapKey];
|
|
1536
|
+
if (!data) {
|
|
1537
|
+
throw new Error(`Expected "${this.wrapKey}" in response`);
|
|
1538
|
+
}
|
|
1539
|
+
return this.transform(data);
|
|
1540
|
+
}
|
|
1541
|
+
transform(entity) {
|
|
1542
|
+
return entity;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
// src/resources/assessment/line-items.ts
|
|
1547
|
+
class ScopedAssessmentLineItemResource {
|
|
1548
|
+
transport;
|
|
1549
|
+
basePath;
|
|
1550
|
+
constructor(transport, lineItemId) {
|
|
1551
|
+
this.transport = transport;
|
|
1552
|
+
this.basePath = `${transport.paths.gradebook}/assessmentLineItems/${lineItemId}`;
|
|
1553
|
+
}
|
|
1554
|
+
async get() {
|
|
1555
|
+
const response = await this.transport.request(this.basePath);
|
|
1556
|
+
return response.assessmentLineItem;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
class AssessmentLineItemsResourceImpl extends BaseResource {
|
|
1561
|
+
constructor(transport) {
|
|
1562
|
+
super(transport, "gradebook", "/assessmentLineItems");
|
|
1563
|
+
}
|
|
1564
|
+
async patch(sourcedId, data) {
|
|
1565
|
+
await this.transport.request(`${this.basePath}/${sourcedId}`, {
|
|
1566
|
+
method: "PATCH",
|
|
1567
|
+
body: { assessmentLineItem: data }
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
get unwrapKey() {
|
|
1571
|
+
return "assessmentLineItems";
|
|
1572
|
+
}
|
|
1573
|
+
get wrapKey() {
|
|
1574
|
+
return "assessmentLineItem";
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
function createAssessmentLineItemsResource(transport) {
|
|
1578
|
+
const impl = new AssessmentLineItemsResourceImpl(transport);
|
|
1579
|
+
const callable = (id) => new ScopedAssessmentLineItemResource(transport, id);
|
|
1580
|
+
callable.list = impl.list.bind(impl);
|
|
1581
|
+
callable.stream = impl.stream.bind(impl);
|
|
1582
|
+
callable.get = impl.get.bind(impl);
|
|
1583
|
+
callable.create = impl.create.bind(impl);
|
|
1584
|
+
callable.update = impl.update.bind(impl);
|
|
1585
|
+
callable.patch = impl.patch.bind(impl);
|
|
1586
|
+
callable.delete = impl.delete.bind(impl);
|
|
1587
|
+
return callable;
|
|
1588
|
+
}
|
|
1589
|
+
// src/resources/assessment/results.ts
|
|
1590
|
+
class AssessmentResultsResource extends BaseResource {
|
|
1591
|
+
constructor(transport) {
|
|
1592
|
+
super(transport, "gradebook", "/assessmentResults");
|
|
1593
|
+
}
|
|
1594
|
+
async patch(sourcedId, data) {
|
|
1595
|
+
await this.transport.request(`${this.basePath}/${sourcedId}`, {
|
|
1596
|
+
method: "PATCH",
|
|
1597
|
+
body: { assessmentResult: data }
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
get unwrapKey() {
|
|
1601
|
+
return "assessmentResults";
|
|
1602
|
+
}
|
|
1603
|
+
get wrapKey() {
|
|
1604
|
+
return "assessmentResult";
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
// src/resources/gradebook/line-items.ts
|
|
1608
|
+
class ScopedLineItemResource {
|
|
1609
|
+
transport;
|
|
1610
|
+
basePath;
|
|
1611
|
+
constructor(transport, lineItemId) {
|
|
1612
|
+
this.transport = transport;
|
|
1613
|
+
this.basePath = `${transport.paths.gradebook}/lineItems/${lineItemId}`;
|
|
1614
|
+
}
|
|
1615
|
+
async get() {
|
|
1616
|
+
const response = await this.transport.request(this.basePath);
|
|
1617
|
+
return response.lineItem;
|
|
1618
|
+
}
|
|
1619
|
+
createResults(results) {
|
|
1620
|
+
return this.transport.request(`${this.basePath}/results`, {
|
|
1621
|
+
method: "POST",
|
|
1622
|
+
body: { results }
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
class LineItemsResourceImpl extends BaseResource {
|
|
1628
|
+
constructor(transport) {
|
|
1629
|
+
super(transport, "gradebook", "/lineItems");
|
|
1630
|
+
}
|
|
1631
|
+
create(data) {
|
|
1632
|
+
return super.create(data);
|
|
1633
|
+
}
|
|
1634
|
+
get unwrapKey() {
|
|
1635
|
+
return "lineItems";
|
|
1636
|
+
}
|
|
1637
|
+
get wrapKey() {
|
|
1638
|
+
return "lineItem";
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
function createLineItemsResource(transport) {
|
|
1642
|
+
const impl = new LineItemsResourceImpl(transport);
|
|
1643
|
+
const callable = (id) => new ScopedLineItemResource(transport, id);
|
|
1644
|
+
callable.list = impl.list.bind(impl);
|
|
1645
|
+
callable.stream = impl.stream.bind(impl);
|
|
1646
|
+
callable.get = impl.get.bind(impl);
|
|
1647
|
+
callable.create = impl.create.bind(impl);
|
|
1648
|
+
callable.update = impl.update.bind(impl);
|
|
1649
|
+
callable.delete = impl.delete.bind(impl);
|
|
1650
|
+
return callable;
|
|
1651
|
+
}
|
|
1652
|
+
// src/resources/gradebook/results.ts
|
|
1653
|
+
class ResultsResource extends BaseResource {
|
|
1654
|
+
constructor(transport) {
|
|
1655
|
+
super(transport, "gradebook", "/results");
|
|
1656
|
+
}
|
|
1657
|
+
create(data) {
|
|
1658
|
+
return super.create(data);
|
|
1659
|
+
}
|
|
1660
|
+
get unwrapKey() {
|
|
1661
|
+
return "results";
|
|
1662
|
+
}
|
|
1663
|
+
get wrapKey() {
|
|
1664
|
+
return "result";
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
// src/resources/gradebook/categories.ts
|
|
1668
|
+
class CategoriesResource extends BaseResource {
|
|
1669
|
+
constructor(transport) {
|
|
1670
|
+
super(transport, "gradebook", "/categories");
|
|
1671
|
+
}
|
|
1672
|
+
get unwrapKey() {
|
|
1673
|
+
return "categories";
|
|
1674
|
+
}
|
|
1675
|
+
get wrapKey() {
|
|
1676
|
+
return "category";
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
// src/resources/gradebook/score-scales.ts
|
|
1680
|
+
class ScoreScalesResource extends BaseResource {
|
|
1681
|
+
constructor(transport) {
|
|
1682
|
+
super(transport, "gradebook", "/scoreScales");
|
|
1683
|
+
}
|
|
1684
|
+
get unwrapKey() {
|
|
1685
|
+
return "scoreScales";
|
|
1686
|
+
}
|
|
1687
|
+
get wrapKey() {
|
|
1688
|
+
return "scoreScale";
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
// src/resources/resources/resources.ts
|
|
1692
|
+
class ScopedResourceResource {
|
|
1693
|
+
transport;
|
|
1694
|
+
basePath;
|
|
1695
|
+
resourceId;
|
|
1696
|
+
constructor(transport, resourceId) {
|
|
1697
|
+
this.transport = transport;
|
|
1698
|
+
this.resourceId = resourceId;
|
|
1699
|
+
this.basePath = `${transport.paths.resources}/resources/${resourceId}`;
|
|
1700
|
+
}
|
|
1701
|
+
async get() {
|
|
1702
|
+
const response = await this.transport.request(this.basePath);
|
|
1703
|
+
return response.resource;
|
|
1704
|
+
}
|
|
1705
|
+
async export() {
|
|
1706
|
+
const response = await this.transport.requestRaw(`${this.transport.paths.resources}/resources/export/${this.resourceId}`, {
|
|
1707
|
+
method: "POST",
|
|
1708
|
+
headers: { Accept: "application/zip" }
|
|
1709
|
+
});
|
|
1710
|
+
return response.arrayBuffer();
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
class ResourcesResourceImpl extends BaseResource {
|
|
1715
|
+
constructor(transport) {
|
|
1716
|
+
super(transport, "resources", "/resources");
|
|
1717
|
+
}
|
|
1718
|
+
async export(resourceId) {
|
|
1719
|
+
const response = await this.transport.requestRaw(`${this.basePath}/export/${resourceId}`, {
|
|
1720
|
+
method: "POST",
|
|
1721
|
+
headers: { Accept: "application/zip" }
|
|
1722
|
+
});
|
|
1723
|
+
return response.arrayBuffer();
|
|
1724
|
+
}
|
|
1725
|
+
get unwrapKey() {
|
|
1726
|
+
return "resources";
|
|
1727
|
+
}
|
|
1728
|
+
get wrapKey() {
|
|
1729
|
+
return "resource";
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
function createResourcesResource(transport) {
|
|
1733
|
+
const impl = new ResourcesResourceImpl(transport);
|
|
1734
|
+
const callable = (id) => new ScopedResourceResource(transport, id);
|
|
1735
|
+
callable.list = impl.list.bind(impl);
|
|
1736
|
+
callable.stream = impl.stream.bind(impl);
|
|
1737
|
+
callable.get = impl.get.bind(impl);
|
|
1738
|
+
callable.create = impl.create.bind(impl);
|
|
1739
|
+
callable.update = impl.update.bind(impl);
|
|
1740
|
+
callable.delete = impl.delete.bind(impl);
|
|
1741
|
+
callable.export = impl.export.bind(impl);
|
|
1742
|
+
return callable;
|
|
1743
|
+
}
|
|
1744
|
+
// src/resources/rostering/users.ts
|
|
1745
|
+
class ScopedUserResource {
|
|
1746
|
+
transport;
|
|
1747
|
+
basePath;
|
|
1748
|
+
userId;
|
|
1749
|
+
constructor(transport, userId) {
|
|
1750
|
+
this.transport = transport;
|
|
1751
|
+
this.userId = userId;
|
|
1752
|
+
this.basePath = `${transport.paths.rostering}/users/${userId}`;
|
|
1753
|
+
}
|
|
1754
|
+
async get() {
|
|
1755
|
+
const response = await this.transport.request(this.basePath);
|
|
1756
|
+
return {
|
|
1757
|
+
...response.user,
|
|
1758
|
+
enabledUser: normalizeBoolean(response.user.enabledUser),
|
|
1759
|
+
grades: parseGrades(response.user.grades)
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
async demographics() {
|
|
1763
|
+
const response = await this.transport.request(`${this.basePath}/demographics`);
|
|
1764
|
+
return {
|
|
1765
|
+
...response.user,
|
|
1766
|
+
enabledUser: normalizeBoolean(response.user.enabledUser),
|
|
1767
|
+
grades: parseGrades(response.user.grades)
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
resources(params) {
|
|
1771
|
+
return this.streamResources(params).toArray();
|
|
1772
|
+
}
|
|
1773
|
+
streamResources(params) {
|
|
1774
|
+
return new Paginator2(this.transport, `${this.transport.paths.resources}/resources/users/${this.userId}/resources`, params, "resources");
|
|
1775
|
+
}
|
|
1776
|
+
classes(params) {
|
|
1777
|
+
return this.streamClasses(params).toArray();
|
|
1778
|
+
}
|
|
1779
|
+
streamClasses(params) {
|
|
1780
|
+
return new Paginator2(this.transport, `${this.basePath}/classes`, params, "classes");
|
|
1781
|
+
}
|
|
1782
|
+
async agentFor() {
|
|
1783
|
+
const response = await this.transport.request(`${this.basePath}/agentFor`);
|
|
1784
|
+
return response.users.map((u) => ({
|
|
1785
|
+
...u,
|
|
1786
|
+
enabledUser: normalizeBoolean(u.enabledUser),
|
|
1787
|
+
grades: parseGrades(u.grades)
|
|
1788
|
+
}));
|
|
1789
|
+
}
|
|
1790
|
+
async agents() {
|
|
1791
|
+
const response = await this.transport.request(`${this.basePath}/agents`);
|
|
1792
|
+
return response.agents.map((u) => ({
|
|
1793
|
+
...u,
|
|
1794
|
+
enabledUser: normalizeBoolean(u.enabledUser),
|
|
1795
|
+
grades: parseGrades(u.grades)
|
|
1796
|
+
}));
|
|
1797
|
+
}
|
|
1798
|
+
addAgent(data) {
|
|
1799
|
+
return this.transport.request(`${this.basePath}/agents`, {
|
|
1800
|
+
method: "POST",
|
|
1801
|
+
body: data
|
|
1802
|
+
});
|
|
1803
|
+
}
|
|
1804
|
+
removeAgent(agentSourcedId) {
|
|
1805
|
+
return this.transport.request(`${this.basePath}/agents/${agentSourcedId}`, {
|
|
1806
|
+
method: "DELETE"
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
createCredential(credential) {
|
|
1810
|
+
return this.transport.request(`${this.basePath}/credentials`, {
|
|
1811
|
+
method: "POST",
|
|
1812
|
+
body: { credential }
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
decryptCredential(credentialId) {
|
|
1816
|
+
return this.transport.request(`${this.basePath}/credentials/${credentialId}/decrypt`, { method: "POST" });
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
class UsersResourceImpl extends BaseResource {
|
|
1821
|
+
constructor(transport) {
|
|
1822
|
+
super(transport, "rostering", "/users");
|
|
1823
|
+
}
|
|
1824
|
+
get unwrapKey() {
|
|
1825
|
+
return "users";
|
|
1826
|
+
}
|
|
1827
|
+
get wrapKey() {
|
|
1828
|
+
return "user";
|
|
1829
|
+
}
|
|
1830
|
+
transform(user) {
|
|
1831
|
+
return {
|
|
1832
|
+
...user,
|
|
1833
|
+
enabledUser: normalizeBoolean(user.enabledUser),
|
|
1834
|
+
grades: parseGrades(user.grades)
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
function createUsersResource(transport) {
|
|
1839
|
+
const impl = new UsersResourceImpl(transport);
|
|
1840
|
+
const callable = (id) => new ScopedUserResource(transport, id);
|
|
1841
|
+
callable.list = impl.list.bind(impl);
|
|
1842
|
+
callable.stream = impl.stream.bind(impl);
|
|
1843
|
+
callable.get = impl.get.bind(impl);
|
|
1844
|
+
callable.create = impl.create.bind(impl);
|
|
1845
|
+
callable.update = impl.update.bind(impl);
|
|
1846
|
+
callable.delete = impl.delete.bind(impl);
|
|
1847
|
+
return callable;
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
class ScopedStudentResource {
|
|
1851
|
+
transport;
|
|
1852
|
+
basePath;
|
|
1853
|
+
constructor(transport, studentId) {
|
|
1854
|
+
this.transport = transport;
|
|
1855
|
+
this.basePath = `${transport.paths.rostering}/students/${studentId}`;
|
|
1856
|
+
}
|
|
1857
|
+
async get() {
|
|
1858
|
+
const response = await this.transport.request(this.basePath);
|
|
1859
|
+
return {
|
|
1860
|
+
...response.user,
|
|
1861
|
+
enabledUser: normalizeBoolean(response.user.enabledUser),
|
|
1862
|
+
grades: parseGrades(response.user.grades)
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
classes(params) {
|
|
1866
|
+
return this.streamClasses(params).toArray();
|
|
1867
|
+
}
|
|
1868
|
+
streamClasses(params) {
|
|
1869
|
+
return new Paginator2(this.transport, `${this.basePath}/classes`, params, "classes");
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
class ScopedTeacherResource {
|
|
1874
|
+
transport;
|
|
1875
|
+
basePath;
|
|
1876
|
+
constructor(transport, teacherId) {
|
|
1877
|
+
this.transport = transport;
|
|
1878
|
+
this.basePath = `${transport.paths.rostering}/teachers/${teacherId}`;
|
|
1879
|
+
}
|
|
1880
|
+
async get() {
|
|
1881
|
+
const response = await this.transport.request(this.basePath);
|
|
1882
|
+
return {
|
|
1883
|
+
...response.user,
|
|
1884
|
+
enabledUser: normalizeBoolean(response.user.enabledUser),
|
|
1885
|
+
grades: parseGrades(response.user.grades)
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
classes(params) {
|
|
1889
|
+
return this.streamClasses(params).toArray();
|
|
1890
|
+
}
|
|
1891
|
+
streamClasses(params) {
|
|
1892
|
+
return new Paginator2(this.transport, `${this.basePath}/classes`, params, "classes");
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
class StudentsResourceImpl extends ReadOnlyResource {
|
|
1897
|
+
constructor(transport) {
|
|
1898
|
+
super(transport, "rostering", "/students");
|
|
1899
|
+
}
|
|
1900
|
+
get unwrapKey() {
|
|
1901
|
+
return "users";
|
|
1902
|
+
}
|
|
1903
|
+
get wrapKey() {
|
|
1904
|
+
return "user";
|
|
1905
|
+
}
|
|
1906
|
+
transform(user) {
|
|
1907
|
+
return {
|
|
1908
|
+
...user,
|
|
1909
|
+
enabledUser: normalizeBoolean(user.enabledUser),
|
|
1910
|
+
grades: parseGrades(user.grades)
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
function createStudentsResource(transport) {
|
|
1915
|
+
const impl = new StudentsResourceImpl(transport);
|
|
1916
|
+
const callable = (id) => new ScopedStudentResource(transport, id);
|
|
1917
|
+
callable.list = impl.list.bind(impl);
|
|
1918
|
+
callable.stream = impl.stream.bind(impl);
|
|
1919
|
+
callable.get = impl.get.bind(impl);
|
|
1920
|
+
return callable;
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
class TeachersResourceImpl extends ReadOnlyResource {
|
|
1924
|
+
constructor(transport) {
|
|
1925
|
+
super(transport, "rostering", "/teachers");
|
|
1926
|
+
}
|
|
1927
|
+
get unwrapKey() {
|
|
1928
|
+
return "users";
|
|
1929
|
+
}
|
|
1930
|
+
get wrapKey() {
|
|
1931
|
+
return "user";
|
|
1932
|
+
}
|
|
1933
|
+
transform(user) {
|
|
1934
|
+
return {
|
|
1935
|
+
...user,
|
|
1936
|
+
enabledUser: normalizeBoolean(user.enabledUser),
|
|
1937
|
+
grades: parseGrades(user.grades)
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
function createTeachersResource(transport) {
|
|
1942
|
+
const impl = new TeachersResourceImpl(transport);
|
|
1943
|
+
const callable = (id) => new ScopedTeacherResource(transport, id);
|
|
1944
|
+
callable.list = impl.list.bind(impl);
|
|
1945
|
+
callable.stream = impl.stream.bind(impl);
|
|
1946
|
+
callable.get = impl.get.bind(impl);
|
|
1947
|
+
return callable;
|
|
1948
|
+
}
|
|
1949
|
+
// src/resources/rostering/orgs.ts
|
|
1950
|
+
class OrgsResource extends BaseResource {
|
|
1951
|
+
constructor(transport) {
|
|
1952
|
+
super(transport, "rostering", "/orgs");
|
|
1953
|
+
}
|
|
1954
|
+
get unwrapKey() {
|
|
1955
|
+
return "orgs";
|
|
1956
|
+
}
|
|
1957
|
+
get wrapKey() {
|
|
1958
|
+
return "org";
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
// src/resources/rostering/schools.ts
|
|
1962
|
+
class ScopedSchoolClassResource {
|
|
1963
|
+
transport;
|
|
1964
|
+
basePath;
|
|
1965
|
+
constructor(transport, schoolId, classId) {
|
|
1966
|
+
this.transport = transport;
|
|
1967
|
+
this.basePath = `${transport.paths.rostering}/schools/${schoolId}/classes/${classId}`;
|
|
1968
|
+
}
|
|
1969
|
+
enrollments(params) {
|
|
1970
|
+
return this.streamEnrollments(params).toArray();
|
|
1971
|
+
}
|
|
1972
|
+
streamEnrollments(params) {
|
|
1973
|
+
return new Paginator2(this.transport, `${this.basePath}/enrollments`, params, "enrollments");
|
|
1974
|
+
}
|
|
1975
|
+
students(params) {
|
|
1976
|
+
return this.streamStudents(params).toArray();
|
|
1977
|
+
}
|
|
1978
|
+
streamStudents(params) {
|
|
1979
|
+
return new Paginator2(this.transport, `${this.basePath}/students`, params, "users");
|
|
1980
|
+
}
|
|
1981
|
+
teachers(params) {
|
|
1982
|
+
return this.streamTeachers(params).toArray();
|
|
1983
|
+
}
|
|
1984
|
+
streamTeachers(params) {
|
|
1985
|
+
return new Paginator2(this.transport, `${this.basePath}/teachers`, params, "users");
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
class ScopedSchoolResource {
|
|
1990
|
+
transport;
|
|
1991
|
+
basePath;
|
|
1992
|
+
gradebookPath;
|
|
1993
|
+
schoolId;
|
|
1994
|
+
constructor(transport, schoolId) {
|
|
1995
|
+
this.transport = transport;
|
|
1996
|
+
this.schoolId = schoolId;
|
|
1997
|
+
this.basePath = `${transport.paths.rostering}/schools/${schoolId}`;
|
|
1998
|
+
this.gradebookPath = `${transport.paths.gradebook}/schools/${schoolId}`;
|
|
1999
|
+
}
|
|
2000
|
+
async get() {
|
|
2001
|
+
const response = await this.transport.request(this.basePath);
|
|
2002
|
+
return response.org;
|
|
2003
|
+
}
|
|
2004
|
+
class(classId) {
|
|
2005
|
+
return new ScopedSchoolClassResource(this.transport, this.schoolId, classId);
|
|
2006
|
+
}
|
|
2007
|
+
classes(params) {
|
|
2008
|
+
return this.streamClasses(params).toArray();
|
|
2009
|
+
}
|
|
2010
|
+
streamClasses(params) {
|
|
2011
|
+
return new Paginator2(this.transport, `${this.basePath}/classes`, params, "classes");
|
|
2012
|
+
}
|
|
2013
|
+
enrollments(params) {
|
|
2014
|
+
return this.streamEnrollments(params).toArray();
|
|
2015
|
+
}
|
|
2016
|
+
streamEnrollments(params) {
|
|
2017
|
+
return new Paginator2(this.transport, `${this.basePath}/enrollments`, params, "enrollments");
|
|
2018
|
+
}
|
|
2019
|
+
students(params) {
|
|
2020
|
+
return this.streamStudents(params).toArray();
|
|
2021
|
+
}
|
|
2022
|
+
streamStudents(params) {
|
|
2023
|
+
return new Paginator2(this.transport, `${this.basePath}/students`, params, "users");
|
|
2024
|
+
}
|
|
2025
|
+
teachers(params) {
|
|
2026
|
+
return this.streamTeachers(params).toArray();
|
|
2027
|
+
}
|
|
2028
|
+
streamTeachers(params) {
|
|
2029
|
+
return new Paginator2(this.transport, `${this.basePath}/teachers`, params, "users");
|
|
2030
|
+
}
|
|
2031
|
+
courses(params) {
|
|
2032
|
+
return this.streamCourses(params).toArray();
|
|
2033
|
+
}
|
|
2034
|
+
streamCourses(params) {
|
|
2035
|
+
return new Paginator2(this.transport, `${this.basePath}/courses`, params, "courses");
|
|
2036
|
+
}
|
|
2037
|
+
terms(params) {
|
|
2038
|
+
return this.streamTerms(params).toArray();
|
|
2039
|
+
}
|
|
2040
|
+
streamTerms(params) {
|
|
2041
|
+
return new Paginator2(this.transport, `${this.basePath}/terms`, params, "terms", (session) => ({
|
|
2042
|
+
...session,
|
|
2043
|
+
startDate: normalizeDateOnly(session.startDate),
|
|
2044
|
+
endDate: normalizeDateOnly(session.endDate)
|
|
2045
|
+
}));
|
|
2046
|
+
}
|
|
2047
|
+
lineItems(params) {
|
|
2048
|
+
return this.streamLineItems(params).toArray();
|
|
2049
|
+
}
|
|
2050
|
+
streamLineItems(params) {
|
|
2051
|
+
return new Paginator2(this.transport, `${this.gradebookPath}/lineItems`, params, "lineItems");
|
|
2052
|
+
}
|
|
2053
|
+
createLineItem(data) {
|
|
2054
|
+
return this.transport.request(`${this.gradebookPath}/lineItems`, {
|
|
2055
|
+
method: "POST",
|
|
2056
|
+
body: { lineItems: data }
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
scoreScales(params) {
|
|
2060
|
+
return this.streamScoreScales(params).toArray();
|
|
2061
|
+
}
|
|
2062
|
+
streamScoreScales(params) {
|
|
2063
|
+
return new Paginator2(this.transport, `${this.gradebookPath}/scoreScales`, params, "scoreScales");
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
class SchoolsResourceImpl extends BaseResource {
|
|
2068
|
+
constructor(transport) {
|
|
2069
|
+
super(transport, "rostering", "/schools");
|
|
2070
|
+
}
|
|
2071
|
+
get unwrapKey() {
|
|
2072
|
+
return "orgs";
|
|
2073
|
+
}
|
|
2074
|
+
get wrapKey() {
|
|
2075
|
+
return "org";
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
function createSchoolsResource(transport) {
|
|
2079
|
+
const impl = new SchoolsResourceImpl(transport);
|
|
2080
|
+
const callable = (id) => new ScopedSchoolResource(transport, id);
|
|
2081
|
+
callable.list = impl.list.bind(impl);
|
|
2082
|
+
callable.stream = impl.stream.bind(impl);
|
|
2083
|
+
callable.get = impl.get.bind(impl);
|
|
2084
|
+
callable.create = impl.create.bind(impl);
|
|
2085
|
+
callable.update = impl.update.bind(impl);
|
|
2086
|
+
callable.delete = impl.delete.bind(impl);
|
|
2087
|
+
return callable;
|
|
2088
|
+
}
|
|
2089
|
+
// src/resources/rostering/classes.ts
|
|
2090
|
+
class ScopedClassAcademicSessionResource {
|
|
2091
|
+
transport;
|
|
2092
|
+
gradebookPath;
|
|
2093
|
+
constructor(transport, classId, sessionId) {
|
|
2094
|
+
this.transport = transport;
|
|
2095
|
+
this.gradebookPath = `${transport.paths.gradebook}/classes/${classId}/academicSessions/${sessionId}`;
|
|
2096
|
+
}
|
|
2097
|
+
createResult(result) {
|
|
2098
|
+
return this.transport.request(`${this.gradebookPath}/results`, {
|
|
2099
|
+
method: "POST",
|
|
2100
|
+
body: { results: result }
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
class ScopedClassStudentResource {
|
|
2106
|
+
transport;
|
|
2107
|
+
gradebookPath;
|
|
2108
|
+
constructor(transport, classId, studentId) {
|
|
2109
|
+
this.transport = transport;
|
|
2110
|
+
this.gradebookPath = `${transport.paths.gradebook}/classes/${classId}/students/${studentId}`;
|
|
2111
|
+
}
|
|
2112
|
+
results(params) {
|
|
2113
|
+
return this.streamResults(params).toArray();
|
|
2114
|
+
}
|
|
2115
|
+
streamResults(params) {
|
|
2116
|
+
return new Paginator2(this.transport, `${this.gradebookPath}/results`, params, "results");
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
class ScopedClassLineItemResource {
|
|
2121
|
+
transport;
|
|
2122
|
+
gradebookPath;
|
|
2123
|
+
constructor(transport, classId, lineItemId) {
|
|
2124
|
+
this.transport = transport;
|
|
2125
|
+
this.gradebookPath = `${transport.paths.gradebook}/classes/${classId}/lineItems/${lineItemId}`;
|
|
2126
|
+
}
|
|
2127
|
+
results(params) {
|
|
2128
|
+
return this.streamResults(params).toArray();
|
|
2129
|
+
}
|
|
2130
|
+
streamResults(params) {
|
|
2131
|
+
return new Paginator2(this.transport, `${this.gradebookPath}/results`, params, "results");
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
class ScopedClassResource {
|
|
2136
|
+
transport;
|
|
2137
|
+
rosteringPath;
|
|
2138
|
+
gradebookPath;
|
|
2139
|
+
classId;
|
|
2140
|
+
constructor(transport, classId) {
|
|
2141
|
+
this.transport = transport;
|
|
2142
|
+
this.classId = classId;
|
|
2143
|
+
this.rosteringPath = `${transport.paths.rostering}/classes/${classId}`;
|
|
2144
|
+
this.gradebookPath = `${transport.paths.gradebook}/classes/${classId}`;
|
|
2145
|
+
}
|
|
2146
|
+
async get() {
|
|
2147
|
+
const response = await this.transport.request(this.rosteringPath);
|
|
2148
|
+
return response.class;
|
|
2149
|
+
}
|
|
2150
|
+
students(params) {
|
|
2151
|
+
return this.streamStudents(params).toArray();
|
|
2152
|
+
}
|
|
2153
|
+
streamStudents(params) {
|
|
2154
|
+
return new Paginator2(this.transport, `${this.rosteringPath}/students`, params, "users");
|
|
2155
|
+
}
|
|
2156
|
+
teachers(params) {
|
|
2157
|
+
return this.streamTeachers(params).toArray();
|
|
2158
|
+
}
|
|
2159
|
+
streamTeachers(params) {
|
|
2160
|
+
return new Paginator2(this.transport, `${this.rosteringPath}/teachers`, params, "users");
|
|
2161
|
+
}
|
|
2162
|
+
enroll(input) {
|
|
2163
|
+
const endpoint = input.role === "student" ? "students" : "teachers";
|
|
2164
|
+
return this.transport.request(`${this.rosteringPath}/${endpoint}`, {
|
|
2165
|
+
method: "POST",
|
|
2166
|
+
body: {
|
|
2167
|
+
enrollment: {
|
|
2168
|
+
user: { sourcedId: input.sourcedId },
|
|
2169
|
+
...input.primary !== undefined && { primary: input.primary },
|
|
2170
|
+
...input.beginDate && { beginDate: input.beginDate },
|
|
2171
|
+
...input.endDate && { endDate: input.endDate }
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
});
|
|
2175
|
+
}
|
|
2176
|
+
lineItems(params) {
|
|
2177
|
+
return this.streamLineItems(params).toArray();
|
|
2178
|
+
}
|
|
2179
|
+
streamLineItems(params) {
|
|
2180
|
+
return new Paginator2(this.transport, `${this.gradebookPath}/lineItems`, params, "lineItems");
|
|
2181
|
+
}
|
|
2182
|
+
createLineItem(data) {
|
|
2183
|
+
return this.transport.request(`${this.gradebookPath}/lineItems`, {
|
|
2184
|
+
method: "POST",
|
|
2185
|
+
body: { lineItems: data }
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
2188
|
+
results(params) {
|
|
2189
|
+
return this.streamResults(params).toArray();
|
|
2190
|
+
}
|
|
2191
|
+
streamResults(params) {
|
|
2192
|
+
return new Paginator2(this.transport, `${this.gradebookPath}/results`, params, "results");
|
|
2193
|
+
}
|
|
2194
|
+
resources(params) {
|
|
2195
|
+
return this.streamResources(params).toArray();
|
|
2196
|
+
}
|
|
2197
|
+
streamResources(params) {
|
|
2198
|
+
return new Paginator2(this.transport, `${this.transport.paths.resources}/resources/classes/${this.classId}/resources`, params, "resources");
|
|
2199
|
+
}
|
|
2200
|
+
student(studentId) {
|
|
2201
|
+
return new ScopedClassStudentResource(this.transport, this.classId, studentId);
|
|
2202
|
+
}
|
|
2203
|
+
lineItem(lineItemId) {
|
|
2204
|
+
return new ScopedClassLineItemResource(this.transport, this.classId, lineItemId);
|
|
2205
|
+
}
|
|
2206
|
+
academicSession(sessionId) {
|
|
2207
|
+
return new ScopedClassAcademicSessionResource(this.transport, this.classId, sessionId);
|
|
2208
|
+
}
|
|
2209
|
+
scoreScales(params) {
|
|
2210
|
+
return this.streamScoreScales(params).toArray();
|
|
2211
|
+
}
|
|
2212
|
+
streamScoreScales(params) {
|
|
2213
|
+
return new Paginator2(this.transport, `${this.gradebookPath}/scoreScales`, params, "scoreScales");
|
|
2214
|
+
}
|
|
2215
|
+
categories(params) {
|
|
2216
|
+
return this.streamCategories(params).toArray();
|
|
2217
|
+
}
|
|
2218
|
+
streamCategories(params) {
|
|
2219
|
+
return new Paginator2(this.transport, `${this.gradebookPath}/categories`, params, "categories");
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
class ClassesResourceImpl extends BaseResource {
|
|
2224
|
+
constructor(transport) {
|
|
2225
|
+
super(transport, "rostering", "/classes");
|
|
2226
|
+
}
|
|
2227
|
+
create(data) {
|
|
2228
|
+
return super.create(data);
|
|
2229
|
+
}
|
|
2230
|
+
get unwrapKey() {
|
|
2231
|
+
return "classes";
|
|
2232
|
+
}
|
|
2233
|
+
get wrapKey() {
|
|
2234
|
+
return "class";
|
|
2235
|
+
}
|
|
2236
|
+
transform(cls) {
|
|
2237
|
+
return {
|
|
2238
|
+
...cls,
|
|
2239
|
+
grades: parseGrades(cls.grades)
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
function createClassesResource(transport) {
|
|
2244
|
+
const impl = new ClassesResourceImpl(transport);
|
|
2245
|
+
const callable = (id) => new ScopedClassResource(transport, id);
|
|
2246
|
+
callable.list = impl.list.bind(impl);
|
|
2247
|
+
callable.stream = impl.stream.bind(impl);
|
|
2248
|
+
callable.get = impl.get.bind(impl);
|
|
2249
|
+
callable.create = impl.create.bind(impl);
|
|
2250
|
+
callable.update = impl.update.bind(impl);
|
|
2251
|
+
callable.delete = impl.delete.bind(impl);
|
|
2252
|
+
return callable;
|
|
2253
|
+
}
|
|
2254
|
+
// src/resources/rostering/courses.ts
|
|
2255
|
+
class ScopedCourseResource {
|
|
2256
|
+
transport;
|
|
2257
|
+
basePath;
|
|
2258
|
+
courseId;
|
|
2259
|
+
constructor(transport, courseId) {
|
|
2260
|
+
this.transport = transport;
|
|
2261
|
+
this.courseId = courseId;
|
|
2262
|
+
this.basePath = `${transport.paths.rostering}/courses/${courseId}`;
|
|
2263
|
+
}
|
|
2264
|
+
async get() {
|
|
2265
|
+
const response = await this.transport.request(this.basePath);
|
|
2266
|
+
return response.course;
|
|
2267
|
+
}
|
|
2268
|
+
classes(params) {
|
|
2269
|
+
return this.streamClasses(params).toArray();
|
|
2270
|
+
}
|
|
2271
|
+
streamClasses(params) {
|
|
2272
|
+
return new Paginator2(this.transport, `${this.basePath}/classes`, params, "classes");
|
|
2273
|
+
}
|
|
2274
|
+
components(params) {
|
|
2275
|
+
return this.streamComponents(params).toArray();
|
|
2276
|
+
}
|
|
2277
|
+
streamComponents(params) {
|
|
2278
|
+
const mergedParams = {
|
|
2279
|
+
...params,
|
|
2280
|
+
where: { ...params?.where, "course.sourcedId": this.courseId }
|
|
2281
|
+
};
|
|
2282
|
+
return new Paginator2(this.transport, `${this.transport.paths.rostering}/courses/components`, mergedParams, "courseComponents");
|
|
2283
|
+
}
|
|
2284
|
+
resources(params) {
|
|
2285
|
+
return this.streamResources(params).toArray();
|
|
2286
|
+
}
|
|
2287
|
+
streamResources(params) {
|
|
2288
|
+
return new Paginator2(this.transport, `${this.transport.paths.resources}/resources/courses/${this.courseId}/resources`, params, "resources");
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
class CoursesResourceImpl extends BaseResource {
|
|
2293
|
+
constructor(transport) {
|
|
2294
|
+
super(transport, "rostering", "/courses");
|
|
2295
|
+
}
|
|
2296
|
+
create(data) {
|
|
2297
|
+
return super.create(data);
|
|
2298
|
+
}
|
|
2299
|
+
update(sourcedId, data) {
|
|
2300
|
+
return super.update(sourcedId, data);
|
|
2301
|
+
}
|
|
2302
|
+
components(params) {
|
|
2303
|
+
return this.streamComponents(params).toArray();
|
|
2304
|
+
}
|
|
2305
|
+
streamComponents(params) {
|
|
2306
|
+
return new Paginator2(this.transport, `${this.basePath}/components`, params, "courseComponents");
|
|
2307
|
+
}
|
|
2308
|
+
async getComponent(sourcedId) {
|
|
2309
|
+
const response = await this.transport.request(`${this.basePath}/components/${sourcedId}`);
|
|
2310
|
+
return response.courseComponent;
|
|
2311
|
+
}
|
|
2312
|
+
createComponent(data) {
|
|
2313
|
+
return this.transport.request(`${this.basePath}/components`, {
|
|
2314
|
+
method: "POST",
|
|
2315
|
+
body: { courseComponent: data }
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
updateComponent(sourcedId, data) {
|
|
2319
|
+
return this.transport.request(`${this.basePath}/components/${sourcedId}`, {
|
|
2320
|
+
method: "PUT",
|
|
2321
|
+
body: { courseComponent: data }
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
2324
|
+
deleteComponent(sourcedId) {
|
|
2325
|
+
return this.transport.request(`${this.basePath}/components/${sourcedId}`, {
|
|
2326
|
+
method: "DELETE"
|
|
2327
|
+
});
|
|
2328
|
+
}
|
|
2329
|
+
componentResources(params) {
|
|
2330
|
+
return this.streamComponentResources(params).toArray();
|
|
2331
|
+
}
|
|
2332
|
+
streamComponentResources(params) {
|
|
2333
|
+
return new Paginator2(this.transport, `${this.basePath}/component-resources`, params, "componentResources");
|
|
2334
|
+
}
|
|
2335
|
+
async getComponentResource(sourcedId) {
|
|
2336
|
+
const response = await this.transport.request(`${this.basePath}/component-resources/${sourcedId}`);
|
|
2337
|
+
return response.componentResource;
|
|
2338
|
+
}
|
|
2339
|
+
createComponentResource(data) {
|
|
2340
|
+
return this.transport.request(`${this.basePath}/component-resources`, {
|
|
2341
|
+
method: "POST",
|
|
2342
|
+
body: { componentResource: data }
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
updateComponentResource(sourcedId, data) {
|
|
2346
|
+
return this.transport.request(`${this.basePath}/component-resources/${sourcedId}`, {
|
|
2347
|
+
method: "PUT",
|
|
2348
|
+
body: { componentResource: data }
|
|
2349
|
+
});
|
|
2350
|
+
}
|
|
2351
|
+
deleteComponentResource(sourcedId) {
|
|
2352
|
+
return this.transport.request(`${this.basePath}/component-resources/${sourcedId}`, {
|
|
2353
|
+
method: "DELETE"
|
|
2354
|
+
});
|
|
2355
|
+
}
|
|
2356
|
+
createStructure(data) {
|
|
2357
|
+
return this.transport.request(`${this.basePath}/structure`, {
|
|
2358
|
+
method: "POST",
|
|
2359
|
+
body: data
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2362
|
+
get unwrapKey() {
|
|
2363
|
+
return "courses";
|
|
2364
|
+
}
|
|
2365
|
+
get wrapKey() {
|
|
2366
|
+
return "course";
|
|
2367
|
+
}
|
|
2368
|
+
transform(course) {
|
|
2369
|
+
return {
|
|
2370
|
+
...course,
|
|
2371
|
+
grades: parseGrades(course.grades)
|
|
2372
|
+
};
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
function createCoursesResource(transport) {
|
|
2376
|
+
const impl = new CoursesResourceImpl(transport);
|
|
2377
|
+
const callable = (id) => new ScopedCourseResource(transport, id);
|
|
2378
|
+
callable.list = impl.list.bind(impl);
|
|
2379
|
+
callable.stream = impl.stream.bind(impl);
|
|
2380
|
+
callable.get = impl.get.bind(impl);
|
|
2381
|
+
callable.create = impl.create.bind(impl);
|
|
2382
|
+
callable.update = impl.update.bind(impl);
|
|
2383
|
+
callable.delete = impl.delete.bind(impl);
|
|
2384
|
+
callable.components = impl.components.bind(impl);
|
|
2385
|
+
callable.streamComponents = impl.streamComponents.bind(impl);
|
|
2386
|
+
callable.getComponent = impl.getComponent.bind(impl);
|
|
2387
|
+
callable.createComponent = impl.createComponent.bind(impl);
|
|
2388
|
+
callable.updateComponent = impl.updateComponent.bind(impl);
|
|
2389
|
+
callable.deleteComponent = impl.deleteComponent.bind(impl);
|
|
2390
|
+
callable.componentResources = impl.componentResources.bind(impl);
|
|
2391
|
+
callable.streamComponentResources = impl.streamComponentResources.bind(impl);
|
|
2392
|
+
callable.getComponentResource = impl.getComponentResource.bind(impl);
|
|
2393
|
+
callable.createComponentResource = impl.createComponentResource.bind(impl);
|
|
2394
|
+
callable.updateComponentResource = impl.updateComponentResource.bind(impl);
|
|
2395
|
+
callable.deleteComponentResource = impl.deleteComponentResource.bind(impl);
|
|
2396
|
+
callable.createStructure = impl.createStructure.bind(impl);
|
|
2397
|
+
return callable;
|
|
2398
|
+
}
|
|
2399
|
+
// src/resources/rostering/enrollments.ts
|
|
2400
|
+
class EnrollmentsResource extends BaseResource {
|
|
2401
|
+
constructor(transport) {
|
|
2402
|
+
super(transport, "rostering", "/enrollments");
|
|
2403
|
+
}
|
|
2404
|
+
async patch(sourcedId, data) {
|
|
2405
|
+
await this.transport.request(`${this.basePath}/${sourcedId}`, {
|
|
2406
|
+
method: "PATCH",
|
|
2407
|
+
body: { [this.wrapKey]: data }
|
|
2408
|
+
});
|
|
2409
|
+
}
|
|
2410
|
+
get unwrapKey() {
|
|
2411
|
+
return "enrollments";
|
|
2412
|
+
}
|
|
2413
|
+
get wrapKey() {
|
|
2414
|
+
return "enrollment";
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
// src/resources/rostering/academic-sessions.ts
|
|
2418
|
+
class AcademicSessionsResource extends BaseResource {
|
|
2419
|
+
constructor(transport) {
|
|
2420
|
+
super(transport, "rostering", "/academicSessions");
|
|
2421
|
+
}
|
|
2422
|
+
get unwrapKey() {
|
|
2423
|
+
return "academicSessions";
|
|
2424
|
+
}
|
|
2425
|
+
get wrapKey() {
|
|
2426
|
+
return "academicSession";
|
|
2427
|
+
}
|
|
2428
|
+
transform(session) {
|
|
2429
|
+
session.startDate = normalizeDateOnly(session.startDate);
|
|
2430
|
+
session.endDate = normalizeDateOnly(session.endDate);
|
|
2431
|
+
return session;
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
class ScopedTermResource {
|
|
2436
|
+
transport;
|
|
2437
|
+
basePath;
|
|
2438
|
+
constructor(transport, termId) {
|
|
2439
|
+
this.transport = transport;
|
|
2440
|
+
this.basePath = `${transport.paths.rostering}/terms/${termId}`;
|
|
2441
|
+
}
|
|
2442
|
+
async get() {
|
|
2443
|
+
const response = await this.transport.request(this.basePath);
|
|
2444
|
+
return response.term;
|
|
2445
|
+
}
|
|
2446
|
+
classes(params) {
|
|
2447
|
+
return this.streamClasses(params).toArray();
|
|
2448
|
+
}
|
|
2449
|
+
streamClasses(params) {
|
|
2450
|
+
return new Paginator2(this.transport, `${this.basePath}/classes`, params, "classes");
|
|
2451
|
+
}
|
|
2452
|
+
gradingPeriods(params) {
|
|
2453
|
+
return this.streamGradingPeriods(params).toArray();
|
|
2454
|
+
}
|
|
2455
|
+
streamGradingPeriods(params) {
|
|
2456
|
+
return new Paginator2(this.transport, `${this.basePath}/gradingPeriods`, params, "gradingPeriods");
|
|
2457
|
+
}
|
|
2458
|
+
async createGradingPeriod(data) {
|
|
2459
|
+
const response = await this.transport.request(`${this.basePath}/gradingPeriods`, {
|
|
2460
|
+
method: "POST",
|
|
2461
|
+
body: { academicSession: data }
|
|
2462
|
+
});
|
|
2463
|
+
return response.academicSession;
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
class TermsResourceImpl extends BaseResource {
|
|
2468
|
+
constructor(transport) {
|
|
2469
|
+
super(transport, "rostering", "/terms");
|
|
2470
|
+
}
|
|
2471
|
+
get unwrapKey() {
|
|
2472
|
+
return "terms";
|
|
2473
|
+
}
|
|
2474
|
+
get wrapKey() {
|
|
2475
|
+
return "term";
|
|
2476
|
+
}
|
|
2477
|
+
transform(term) {
|
|
2478
|
+
term.startDate = normalizeDateOnly(term.startDate);
|
|
2479
|
+
term.endDate = normalizeDateOnly(term.endDate);
|
|
2480
|
+
return term;
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
function createTermsResource(transport) {
|
|
2484
|
+
const impl = new TermsResourceImpl(transport);
|
|
2485
|
+
const callable = (id) => new ScopedTermResource(transport, id);
|
|
2486
|
+
callable.list = impl.list.bind(impl);
|
|
2487
|
+
callable.stream = impl.stream.bind(impl);
|
|
2488
|
+
callable.get = impl.get.bind(impl);
|
|
2489
|
+
return callable;
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
class GradingPeriodsResource extends BaseResource {
|
|
2493
|
+
constructor(transport) {
|
|
2494
|
+
super(transport, "rostering", "/gradingPeriods");
|
|
2495
|
+
}
|
|
2496
|
+
get unwrapKey() {
|
|
2497
|
+
return "gradingPeriods";
|
|
2498
|
+
}
|
|
2499
|
+
get wrapKey() {
|
|
2500
|
+
return "gradingPeriod";
|
|
2501
|
+
}
|
|
2502
|
+
transform(gp) {
|
|
2503
|
+
return {
|
|
2504
|
+
...gp,
|
|
2505
|
+
startDate: normalizeDateOnly(gp.startDate),
|
|
2506
|
+
endDate: normalizeDateOnly(gp.endDate)
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
wrapBody(data) {
|
|
2510
|
+
return { academicSession: data };
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
// src/resources/rostering/demographics.ts
|
|
2514
|
+
class DemographicsResource extends BaseResource {
|
|
2515
|
+
constructor(transport) {
|
|
2516
|
+
super(transport, "rostering", "/demographics");
|
|
2517
|
+
}
|
|
2518
|
+
get unwrapKey() {
|
|
2519
|
+
return "demographics";
|
|
2520
|
+
}
|
|
2521
|
+
get wrapKey() {
|
|
2522
|
+
return "demographics";
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
// src/factory.ts
|
|
2526
|
+
function createOneRosterClient(registry = DEFAULT_PROVIDER_REGISTRY) {
|
|
2527
|
+
return class OneRosterClient {
|
|
2528
|
+
transport;
|
|
2529
|
+
_provider;
|
|
2530
|
+
users;
|
|
2531
|
+
students;
|
|
2532
|
+
teachers;
|
|
2533
|
+
orgs;
|
|
2534
|
+
schools;
|
|
2535
|
+
classes;
|
|
2536
|
+
courses;
|
|
2537
|
+
enrollments;
|
|
2538
|
+
academicSessions;
|
|
2539
|
+
terms;
|
|
2540
|
+
gradingPeriods;
|
|
2541
|
+
demographics;
|
|
2542
|
+
lineItems;
|
|
2543
|
+
results;
|
|
2544
|
+
categories;
|
|
2545
|
+
scoreScales;
|
|
2546
|
+
assessmentLineItems;
|
|
2547
|
+
assessmentResults;
|
|
2548
|
+
resources;
|
|
2549
|
+
constructor(config = {}) {
|
|
2550
|
+
const resolved = resolveToProvider2(config, registry);
|
|
2551
|
+
if (resolved.mode === "transport") {
|
|
2552
|
+
this.transport = resolved.transport;
|
|
2553
|
+
log2.info("Client initialized with custom transport");
|
|
2554
|
+
} else {
|
|
2555
|
+
const { provider } = resolved;
|
|
2556
|
+
const { baseUrl, paths } = provider.getEndpointWithPaths("oneroster");
|
|
2557
|
+
const tokenProvider = provider.getTokenProvider("oneroster");
|
|
2558
|
+
if (!tokenProvider) {
|
|
2559
|
+
throw new Error("OneRoster API requires authentication");
|
|
2560
|
+
}
|
|
2561
|
+
this._provider = provider;
|
|
2562
|
+
this.transport = new Transport({
|
|
2563
|
+
baseUrl,
|
|
2564
|
+
tokenProvider,
|
|
2565
|
+
timeout: provider.timeout,
|
|
2566
|
+
paths
|
|
2567
|
+
});
|
|
2568
|
+
log2.info("Client initialized", {
|
|
2569
|
+
platform: provider.platform,
|
|
2570
|
+
env: provider.env,
|
|
2571
|
+
baseUrl
|
|
2572
|
+
});
|
|
2573
|
+
}
|
|
2574
|
+
this.users = createUsersResource(this.transport);
|
|
2575
|
+
this.students = createStudentsResource(this.transport);
|
|
2576
|
+
this.teachers = createTeachersResource(this.transport);
|
|
2577
|
+
this.orgs = new OrgsResource(this.transport);
|
|
2578
|
+
this.schools = createSchoolsResource(this.transport);
|
|
2579
|
+
this.classes = createClassesResource(this.transport);
|
|
2580
|
+
this.courses = createCoursesResource(this.transport);
|
|
2581
|
+
this.enrollments = new EnrollmentsResource(this.transport);
|
|
2582
|
+
this.academicSessions = new AcademicSessionsResource(this.transport);
|
|
2583
|
+
this.terms = createTermsResource(this.transport);
|
|
2584
|
+
this.gradingPeriods = new GradingPeriodsResource(this.transport);
|
|
2585
|
+
this.demographics = new DemographicsResource(this.transport);
|
|
2586
|
+
this.lineItems = createLineItemsResource(this.transport);
|
|
2587
|
+
this.results = new ResultsResource(this.transport);
|
|
2588
|
+
this.categories = new CategoriesResource(this.transport);
|
|
2589
|
+
this.scoreScales = new ScoreScalesResource(this.transport);
|
|
2590
|
+
this.assessmentLineItems = createAssessmentLineItemsResource(this.transport);
|
|
2591
|
+
this.assessmentResults = new AssessmentResultsResource(this.transport);
|
|
2592
|
+
this.resources = createResourcesResource(this.transport);
|
|
2593
|
+
}
|
|
2594
|
+
getTransport() {
|
|
2595
|
+
return this.transport;
|
|
2596
|
+
}
|
|
2597
|
+
checkAuth() {
|
|
2598
|
+
if (!this._provider) {
|
|
2599
|
+
throw new Error("Cannot check auth: client initialized with custom transport");
|
|
2600
|
+
}
|
|
2601
|
+
return this._provider.checkAuth();
|
|
2602
|
+
}
|
|
2603
|
+
};
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
// src/client.ts
|
|
2607
|
+
var OneRosterClient = createOneRosterClient();
|
|
2608
|
+
export {
|
|
2609
|
+
whereToFilter,
|
|
2610
|
+
createOneRosterClient,
|
|
2611
|
+
Transport,
|
|
2612
|
+
Paginator2 as Paginator,
|
|
2613
|
+
OneRosterClient
|
|
2614
|
+
};
|