@kainoa/5c-mcp 0.4.0 → 0.4.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kainoa/5c-mcp",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "5C Index MCP server — 5 tools over the shared 5c-core engine.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,258 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // ../core/dist/chunk-M5LVN3BK.js
4
- import { z } from "zod";
5
- var rawDateSchema = z.object({
6
- year: z.number(),
7
- month: z.number().min(1).max(12),
8
- day: z.number().min(1).max(31),
9
- });
10
- var rawCourseCodeSchema = z.object({
11
- department: z.string(),
12
- courseNumber: z.number(),
13
- suffix: z.string(),
14
- affiliation: z.string(),
15
- });
16
- var rawScheduleSchema = z.object({
17
- startTime: z.number(),
18
- // seconds since midnight
19
- endTime: z.number(),
20
- days: z.array(z.string()),
21
- locations: z.array(z.string()),
22
- });
23
- var rawSectionSchema = z.object({
24
- courseAreas: z.array(z.string()),
25
- credits: z.number(),
26
- permCount: z.number().default(0),
27
- seatsTotal: z.number(),
28
- seatsFilled: z.number(),
29
- status: z.string(),
30
- // O | C | R | U (keep raw; map to labels downstream)
31
- startDate: rawDateSchema,
32
- endDate: rawDateSchema,
33
- instructors: z.array(z.object({ name: z.string() })),
34
- course: z.object({
35
- code: rawCourseCodeSchema,
36
- title: z.string(),
37
- description: z.string(),
38
- primaryAssociation: z.string(),
39
- potentialError: z.boolean().default(false),
40
- }),
41
- schedules: z.array(rawScheduleSchema),
42
- potentialError: z.boolean().default(false),
43
- identifier: z.object({
44
- department: z.string(),
45
- courseNumber: z.number(),
46
- suffix: z.string(),
47
- affiliation: z.string(),
48
- sectionNumber: z.number(),
49
- year: z.number(),
50
- term: z.string(),
51
- // FA | SP | SU
52
- half: z.nullable(z.object({ prefix: z.string(), number: z.number() })),
53
- }),
54
- });
55
- var rawSectionsSchema = z.array(rawSectionSchema);
56
- var rawHistoryEntrySchema = z.object({
57
- code: rawCourseCodeSchema,
58
- terms: z.array(z.object({ year: z.number(), term: z.string() })),
59
- });
60
- var rawHistorySchema = z.array(rawHistoryEntrySchema);
61
- var rawTermSchema = z.object({ year: z.number(), term: z.string() });
62
- var rawCourseAreaSchema = z.object({
63
- area: z.string(),
64
- description: z.string(),
65
- });
66
- var termSchema = z
67
- .string()
68
- .regex(/^(FA|SP|SU)\d{4}$/, "Term must look like FA2026");
69
- var daySchema = z.enum(["M", "T", "W", "R", "F", "S", "U"]);
70
- var statusSchema = z.enum(["O", "C", "R", "U"]);
71
- var meetingSchema = z.object({
72
- days: z.array(daySchema),
73
- startMin: z.number(),
74
- // minutes since midnight (math)
75
- endMin: z.number(),
76
- startStr: z.string(),
77
- // "13:15" (quote)
78
- endStr: z.string(),
79
- start12: z.string(),
80
- // "1:15 PM" (display)
81
- end12: z.string(),
82
- locations: z.array(z.string()),
83
- });
84
- var sectionSchema = z.object({
85
- id: z.string(),
86
- // "CSCI 051 PO-01 FA2026" (canonical, frozen v1)
87
- term: termSchema,
88
- year: z.number(),
89
- termCode: z.enum(["FA", "SP", "SU"]),
90
- half: z.nullable(z.object({ prefix: z.string(), number: z.number() })),
91
- dept: z.string(),
92
- number: z.number(),
93
- suffix: z.string(),
94
- school: z.string(),
95
- // affiliation, uppercase (PO/CM/HM/SC/PZ + KS/JP/…)
96
- sectionNo: z.number(),
97
- code: z.string(),
98
- // "CSCI 051" (dept + zero-padded + suffix, no school)
99
- title: z.string(),
100
- description: z.string(),
101
- areas: z.array(z.string()),
102
- instructors: z.array(z.string()),
103
- meetings: z.array(meetingSchema),
104
- credits: z.number(),
105
- status: statusSchema,
106
- statusLabel: z.string(),
107
- // Open | Closed | Reopened | Unknown
108
- seats: z.object({
109
- filled: z.number(),
110
- total: z.number(),
111
- available: z.number(),
112
- permCount: z.number(),
113
- }),
114
- startDate: z.string(),
115
- // ISO
116
- endDate: z.string(),
117
- potentialError: z.boolean(),
118
- primaryAssociation: z.string(),
119
- affiliation: z.string(),
120
- });
121
- var cacheMetaSchema = z.object({
122
- term: z.string(),
123
- fetched_at: z.string(),
124
- etag: z.string().nullable(),
125
- hash: z.string(),
126
- count: z.number(),
127
- source_url: z.string(),
128
- ttl_kind: z.string(),
129
- stale: z.boolean(),
130
- source: z.enum(["live", "stale-cache", "offline-file"]),
131
- });
132
- var BANANA_BASE = "https://banana.hyperschedule.io/v4";
133
- var USER_AGENT = "5C-Index/0.1 (+research/edu; cache 60s)";
134
- async function sleep(ms) {
135
- return new Promise((r) => setTimeout(r, ms));
136
- }
137
- async function fetchJson(url, validate, opts = {}, fetchFn = globalThis.fetch) {
138
- const { etag, timeoutMs = 15e3, retries = 3 } = opts;
139
- let lastErr = null;
140
- for (let attempt = 0; attempt <= retries; attempt++) {
141
- const ctrl = new AbortController();
142
- const timer = setTimeout(() => ctrl.abort(), timeoutMs);
143
- try {
144
- const res = await fetchFn(url, {
145
- signal: ctrl.signal,
146
- headers: {
147
- Accept: "application/json",
148
- "User-Agent": USER_AGENT,
149
- ...(etag ? { "If-None-Match": etag } : {}),
150
- },
151
- });
152
- clearTimeout(timer);
153
- if (res.status === 304) {
154
- return { data: void 0, etag: etag ?? null, notModified: true };
155
- }
156
- if (res.status === 404) {
157
- const err = new Error(`Not found: ${url}`);
158
- err.code = "E_NOT_FOUND_UPSTREAM";
159
- throw err;
160
- }
161
- if (res.status === 429 || res.status >= 500) {
162
- const retryAfter = res.headers.get("Retry-After");
163
- const wait = retryAfter
164
- ? Math.min(Number.parseInt(retryAfter, 10) * 1e3 || 2e3, 15e3)
165
- : Math.min(500 * 2 ** attempt + Math.random() * 250, 8e3);
166
- lastErr = new Error(`Upstream ${res.status} for ${url}`);
167
- if (attempt < retries) {
168
- await sleep(wait);
169
- continue;
170
- }
171
- throw lastErr;
172
- }
173
- if (!res.ok) {
174
- const err = new Error(`Upstream ${res.status} for ${url}`);
175
- err.code = "E_FETCH";
176
- throw err;
177
- }
178
- const json = await res.json();
179
- return {
180
- data: validate(json),
181
- etag: res.headers.get("ETag"),
182
- notModified: false,
183
- };
184
- } catch (err) {
185
- clearTimeout(timer);
186
- if (err.code === "E_NOT_FOUND_UPSTREAM") throw err;
187
- if (err.name === "AbortError") {
188
- lastErr = new Error(`Timeout fetching ${url}`);
189
- } else {
190
- lastErr = err;
191
- }
192
- if (attempt < retries && (!err.code || err.code === "E_FETCH")) {
193
- if (err.code === "E_FETCH") throw err;
194
- await sleep(Math.min(500 * 2 ** attempt + Math.random() * 250, 8e3));
195
- continue;
196
- }
197
- throw lastErr;
198
- }
199
- }
200
- throw lastErr;
201
- }
202
- function fetchAllTerms(opts, fetchFn) {
203
- return fetchJson(
204
- `${BANANA_BASE}/term/all`,
205
- (u) => rawTermSchema.array().parse(u),
206
- opts,
207
- fetchFn,
208
- );
209
- }
210
- function fetchCurrentTerm(opts, fetchFn) {
211
- return fetchJson(
212
- `${BANANA_BASE}/term/current`,
213
- (u) => rawTermSchema.parse(u),
214
- opts,
215
- fetchFn,
216
- );
217
- }
218
- function fetchSections(term, opts, fetchFn) {
219
- return fetchJson(
220
- `${BANANA_BASE}/sections/${term}`,
221
- (u) => rawSectionsSchema.parse(u),
222
- opts,
223
- fetchFn,
224
- );
225
- }
226
- function fetchOfferingHistory(term, opts, fetchFn) {
227
- return fetchJson(
228
- `${BANANA_BASE}/offering-history/${term}`,
229
- (u) => rawHistorySchema.parse(u),
230
- opts,
231
- fetchFn,
232
- );
233
- }
234
- function fetchCourseAreas(opts, fetchFn) {
235
- return fetchJson(
236
- `${BANANA_BASE}/course-areas`,
237
- (u) =>
238
- u.map((x) => ({
239
- area: String(x.area),
240
- description: String(x.description),
241
- })),
242
- opts,
243
- fetchFn,
244
- );
245
- }
246
-
247
- export {
248
- rawSectionsSchema,
249
- rawHistorySchema,
250
- BANANA_BASE,
251
- USER_AGENT,
252
- fetchJson,
253
- fetchAllTerms,
254
- fetchCurrentTerm,
255
- fetchSections,
256
- fetchOfferingHistory,
257
- fetchCourseAreas,
258
- };
@@ -1,253 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // ../core/dist/chunk-BUUEIEGG.js
4
- import { z } from "zod";
5
- var rawDateSchema = z.object({
6
- year: z.number(),
7
- month: z.number().min(1).max(12),
8
- day: z.number().min(1).max(31)
9
- });
10
- var rawCourseCodeSchema = z.object({
11
- department: z.string(),
12
- courseNumber: z.number(),
13
- suffix: z.string(),
14
- affiliation: z.string()
15
- });
16
- var rawScheduleSchema = z.object({
17
- startTime: z.number(),
18
- // seconds since midnight
19
- endTime: z.number(),
20
- days: z.array(z.string()),
21
- locations: z.array(z.string())
22
- });
23
- var rawSectionSchema = z.object({
24
- courseAreas: z.array(z.string()),
25
- credits: z.number(),
26
- permCount: z.number().default(0),
27
- seatsTotal: z.number(),
28
- seatsFilled: z.number(),
29
- status: z.string(),
30
- // O | C | R | U (keep raw; map to labels downstream)
31
- startDate: rawDateSchema,
32
- endDate: rawDateSchema,
33
- instructors: z.array(z.object({ name: z.string() })),
34
- course: z.object({
35
- code: rawCourseCodeSchema,
36
- title: z.string(),
37
- description: z.string(),
38
- primaryAssociation: z.string(),
39
- potentialError: z.boolean().default(false)
40
- }),
41
- schedules: z.array(rawScheduleSchema),
42
- potentialError: z.boolean().default(false),
43
- identifier: z.object({
44
- department: z.string(),
45
- courseNumber: z.number(),
46
- suffix: z.string(),
47
- affiliation: z.string(),
48
- sectionNumber: z.number(),
49
- year: z.number(),
50
- term: z.string(),
51
- // FA | SP | SU
52
- half: z.nullable(z.object({ prefix: z.string(), number: z.number() }))
53
- })
54
- });
55
- var rawSectionsSchema = z.array(rawSectionSchema);
56
- var rawHistoryEntrySchema = z.object({
57
- code: rawCourseCodeSchema,
58
- terms: z.array(z.object({ year: z.number(), term: z.string() }))
59
- });
60
- var rawHistorySchema = z.array(rawHistoryEntrySchema);
61
- var rawTermSchema = z.object({ year: z.number(), term: z.string() });
62
- var rawCourseAreaSchema = z.object({
63
- area: z.string(),
64
- description: z.string()
65
- });
66
- var termSchema = z.string().regex(/^(FA|SP|SU)\d{4}$/, "Term must look like FA2026");
67
- var daySchema = z.enum(["M", "T", "W", "R", "F", "S", "U"]);
68
- var statusSchema = z.enum(["O", "C", "R", "U"]);
69
- var meetingSchema = z.object({
70
- days: z.array(daySchema),
71
- startMin: z.number(),
72
- // minutes since midnight (math)
73
- endMin: z.number(),
74
- startStr: z.string(),
75
- // "13:15" (quote)
76
- endStr: z.string(),
77
- start12: z.string(),
78
- // "1:15 PM" (display)
79
- end12: z.string(),
80
- locations: z.array(z.string())
81
- });
82
- var sectionSchema = z.object({
83
- id: z.string(),
84
- // "CSCI 051 PO-01 FA2026" (canonical, frozen v1)
85
- term: termSchema,
86
- year: z.number(),
87
- termCode: z.enum(["FA", "SP", "SU"]),
88
- half: z.nullable(z.object({ prefix: z.string(), number: z.number() })),
89
- dept: z.string(),
90
- number: z.number(),
91
- suffix: z.string(),
92
- school: z.string(),
93
- // affiliation, uppercase (PO/CM/HM/SC/PZ + KS/JP/…)
94
- sectionNo: z.number(),
95
- code: z.string(),
96
- // "CSCI 051" (dept + zero-padded + suffix, no school)
97
- title: z.string(),
98
- description: z.string(),
99
- areas: z.array(z.string()),
100
- instructors: z.array(z.string()),
101
- meetings: z.array(meetingSchema),
102
- credits: z.number(),
103
- status: statusSchema,
104
- statusLabel: z.string(),
105
- // Open | Closed | Reopened | Unknown
106
- seats: z.object({
107
- filled: z.number(),
108
- total: z.number(),
109
- available: z.number(),
110
- permCount: z.number()
111
- }),
112
- startDate: z.string(),
113
- // ISO
114
- endDate: z.string(),
115
- potentialError: z.boolean(),
116
- primaryAssociation: z.string(),
117
- affiliation: z.string()
118
- });
119
- var cacheMetaSchema = z.object({
120
- term: z.string(),
121
- fetched_at: z.string(),
122
- etag: z.string().nullable(),
123
- hash: z.string(),
124
- count: z.number(),
125
- source_url: z.string(),
126
- ttl_kind: z.string(),
127
- stale: z.boolean(),
128
- source: z.enum(["live", "stale-cache", "offline-file"])
129
- });
130
- var BANANA_BASE = "https://banana.hyperschedule.io/v4";
131
- var USER_AGENT = "5C-Index/0.1 (+research/edu; cache 60s)";
132
- async function sleep(ms) {
133
- return new Promise((r) => setTimeout(r, ms));
134
- }
135
- async function fetchJson(url, validate, opts = {}, fetchFn = globalThis.fetch) {
136
- const { etag, timeoutMs = 15e3, retries = 3 } = opts;
137
- let lastErr = null;
138
- for (let attempt = 0; attempt <= retries; attempt++) {
139
- const ctrl = new AbortController();
140
- const timer = setTimeout(() => ctrl.abort(), timeoutMs);
141
- try {
142
- const res = await fetchFn(url, {
143
- signal: ctrl.signal,
144
- headers: {
145
- Accept: "application/json",
146
- "User-Agent": USER_AGENT,
147
- ...etag ? { "If-None-Match": etag } : {}
148
- }
149
- });
150
- clearTimeout(timer);
151
- if (res.status === 304) {
152
- return { data: void 0, etag: etag ?? null, notModified: true };
153
- }
154
- if (res.status === 404) {
155
- const err = new Error(`Not found: ${url}`);
156
- err.code = "E_NOT_FOUND_UPSTREAM";
157
- throw err;
158
- }
159
- if (res.status === 429 || res.status >= 500) {
160
- const retryAfter = res.headers.get("Retry-After");
161
- const wait = retryAfter ? Math.min(Number.parseInt(retryAfter, 10) * 1e3 || 2e3, 15e3) : Math.min(500 * 2 ** attempt + Math.random() * 250, 8e3);
162
- lastErr = new Error(`Upstream ${res.status} for ${url}`);
163
- if (attempt < retries) {
164
- await sleep(wait);
165
- continue;
166
- }
167
- throw lastErr;
168
- }
169
- if (!res.ok) {
170
- const err = new Error(`Upstream ${res.status} for ${url}`);
171
- err.code = "E_FETCH";
172
- throw err;
173
- }
174
- const json = await res.json();
175
- return {
176
- data: validate(json),
177
- etag: res.headers.get("ETag"),
178
- notModified: false
179
- };
180
- } catch (err) {
181
- clearTimeout(timer);
182
- if (err.code === "E_NOT_FOUND_UPSTREAM") throw err;
183
- if (err.name === "AbortError") {
184
- lastErr = new Error(`Timeout fetching ${url}`);
185
- } else {
186
- lastErr = err;
187
- }
188
- if (attempt < retries && (!err.code || err.code === "E_FETCH")) {
189
- if (err.code === "E_FETCH") throw err;
190
- await sleep(Math.min(500 * 2 ** attempt + Math.random() * 250, 8e3));
191
- continue;
192
- }
193
- throw lastErr;
194
- }
195
- }
196
- throw lastErr;
197
- }
198
- function fetchAllTerms(opts, fetchFn) {
199
- return fetchJson(
200
- `${BANANA_BASE}/term/all`,
201
- (u) => rawTermSchema.array().parse(u),
202
- opts,
203
- fetchFn
204
- );
205
- }
206
- function fetchCurrentTerm(opts, fetchFn) {
207
- return fetchJson(
208
- `${BANANA_BASE}/term/current`,
209
- (u) => rawTermSchema.parse(u),
210
- opts,
211
- fetchFn
212
- );
213
- }
214
- function fetchSections(term, opts, fetchFn) {
215
- return fetchJson(
216
- `${BANANA_BASE}/sections/${term}`,
217
- (u) => rawSectionsSchema.parse(u),
218
- opts,
219
- fetchFn
220
- );
221
- }
222
- function fetchOfferingHistory(term, opts, fetchFn) {
223
- return fetchJson(
224
- `${BANANA_BASE}/offering-history/${term}`,
225
- (u) => rawHistorySchema.parse(u),
226
- opts,
227
- fetchFn
228
- );
229
- }
230
- function fetchCourseAreas(opts, fetchFn) {
231
- return fetchJson(
232
- `${BANANA_BASE}/course-areas`,
233
- (u) => u.map((x) => ({
234
- area: String(x.area),
235
- description: String(x.description)
236
- })),
237
- opts,
238
- fetchFn
239
- );
240
- }
241
-
242
- export {
243
- rawSectionsSchema,
244
- rawHistorySchema,
245
- BANANA_BASE,
246
- USER_AGENT,
247
- fetchJson,
248
- fetchAllTerms,
249
- fetchCurrentTerm,
250
- fetchSections,
251
- fetchOfferingHistory,
252
- fetchCourseAreas
253
- };
@@ -1,257 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // ../core/dist/chunk-D2NNBE6W.js
4
- import { z } from "zod";
5
- var rawDateSchema = z.object({
6
- year: z.number(),
7
- month: z.number().min(1).max(12),
8
- day: z.number().min(1).max(31)
9
- });
10
- var rawCourseCodeSchema = z.object({
11
- department: z.string(),
12
- courseNumber: z.number(),
13
- suffix: z.string(),
14
- affiliation: z.string()
15
- });
16
- var rawScheduleSchema = z.object({
17
- startTime: z.number(),
18
- // seconds since midnight
19
- endTime: z.number(),
20
- days: z.array(z.string()),
21
- locations: z.array(z.string())
22
- });
23
- var rawSectionSchema = z.object({
24
- courseAreas: z.array(z.string()),
25
- credits: z.number(),
26
- permCount: z.number().default(0),
27
- seatsTotal: z.number(),
28
- seatsFilled: z.number(),
29
- status: z.string(),
30
- // O | C | R | U (keep raw; map to labels downstream)
31
- startDate: rawDateSchema,
32
- endDate: rawDateSchema,
33
- instructors: z.array(z.object({ name: z.string() })),
34
- course: z.object({
35
- code: rawCourseCodeSchema,
36
- title: z.string(),
37
- description: z.string(),
38
- primaryAssociation: z.string(),
39
- potentialError: z.boolean().default(false)
40
- }),
41
- schedules: z.array(rawScheduleSchema),
42
- potentialError: z.boolean().default(false),
43
- identifier: z.object({
44
- department: z.string(),
45
- courseNumber: z.number(),
46
- suffix: z.string(),
47
- affiliation: z.string(),
48
- sectionNumber: z.number(),
49
- year: z.number(),
50
- term: z.string(),
51
- // FA | SP | SU
52
- half: z.nullable(z.object({ prefix: z.string(), number: z.number() }))
53
- })
54
- });
55
- var rawSectionsSchema = z.array(rawSectionSchema);
56
- var rawHistoryEntrySchema = z.object({
57
- code: rawCourseCodeSchema,
58
- terms: z.array(z.object({ year: z.number(), term: z.string() }))
59
- });
60
- var rawHistorySchema = z.array(rawHistoryEntrySchema);
61
- var rawTermSchema = z.object({ year: z.number(), term: z.string() });
62
- var rawCourseAreaSchema = z.object({
63
- area: z.string(),
64
- description: z.string()
65
- });
66
- var termSchema = z.string().regex(/^(FA|SP|SU)\d{4}$/, "Term must look like FA2026");
67
- var daySchema = z.enum(["M", "T", "W", "R", "F", "S", "U"]);
68
- var statusSchema = z.enum(["O", "C", "R", "U"]);
69
- var meetingSchema = z.object({
70
- days: z.array(daySchema),
71
- startMin: z.number(),
72
- // minutes since midnight (math)
73
- endMin: z.number(),
74
- startStr: z.string(),
75
- // "13:15" (quote)
76
- endStr: z.string(),
77
- start12: z.string(),
78
- // "1:15 PM" (display)
79
- end12: z.string(),
80
- locations: z.array(z.string())
81
- });
82
- var sectionSchema = z.object({
83
- id: z.string(),
84
- // "CSCI 051 PO-01 FA2026" (canonical, frozen v1)
85
- term: termSchema,
86
- year: z.number(),
87
- termCode: z.enum(["FA", "SP", "SU"]),
88
- half: z.nullable(z.object({ prefix: z.string(), number: z.number() })),
89
- dept: z.string(),
90
- number: z.number(),
91
- suffix: z.string(),
92
- school: z.string(),
93
- // affiliation, uppercase (PO/CM/HM/SC/PZ + KS/JP/…)
94
- sectionNo: z.number(),
95
- code: z.string(),
96
- // "CSCI 051" (dept + zero-padded + suffix, no school)
97
- title: z.string(),
98
- description: z.string(),
99
- areas: z.array(z.string()),
100
- instructors: z.array(z.string()),
101
- meetings: z.array(meetingSchema),
102
- credits: z.number(),
103
- status: statusSchema,
104
- statusLabel: z.string(),
105
- // Open | Closed | Reopened | Unknown
106
- seats: z.object({
107
- filled: z.number(),
108
- total: z.number(),
109
- available: z.number(),
110
- permCount: z.number()
111
- }),
112
- startDate: z.string(),
113
- // ISO
114
- endDate: z.string(),
115
- potentialError: z.boolean(),
116
- primaryAssociation: z.string(),
117
- affiliation: z.string()
118
- });
119
- var cacheMetaSchema = z.object({
120
- term: z.string(),
121
- fetched_at: z.string(),
122
- etag: z.string().nullable(),
123
- hash: z.string(),
124
- count: z.number(),
125
- source_url: z.string(),
126
- ttl_kind: z.string(),
127
- stale: z.boolean(),
128
- source: z.enum(["live", "stale-cache", "offline-file"]),
129
- quarantined: z.number().int().nonnegative().default(0)
130
- });
131
- var BANANA_BASE = "https://banana.hyperschedule.io/v4";
132
- var USER_AGENT = "5C-Index/0.1 (+research/edu; cache 60s)";
133
- async function sleep(ms) {
134
- return new Promise((r) => setTimeout(r, ms));
135
- }
136
- async function fetchJson(url, validate, opts = {}, fetchFn = globalThis.fetch) {
137
- const { etag, timeoutMs = 15e3, retries = 3 } = opts;
138
- let lastErr = null;
139
- for (let attempt = 0; attempt <= retries; attempt++) {
140
- const ctrl = new AbortController();
141
- const timer = setTimeout(() => ctrl.abort(), timeoutMs);
142
- try {
143
- const res = await fetchFn(url, {
144
- signal: ctrl.signal,
145
- headers: {
146
- Accept: "application/json",
147
- "User-Agent": USER_AGENT,
148
- ...etag ? { "If-None-Match": etag } : {}
149
- }
150
- });
151
- clearTimeout(timer);
152
- if (res.status === 304) {
153
- return { data: void 0, etag: etag ?? null, notModified: true };
154
- }
155
- if (res.status === 404) {
156
- const err = new Error(`Not found: ${url}`);
157
- err.code = "E_NOT_FOUND_UPSTREAM";
158
- throw err;
159
- }
160
- if (res.status === 429 || res.status >= 500) {
161
- const retryAfter = res.headers.get("Retry-After");
162
- const wait = retryAfter ? Math.min(Number.parseInt(retryAfter, 10) * 1e3 || 2e3, 15e3) : Math.min(500 * 2 ** attempt + Math.random() * 250, 8e3);
163
- lastErr = new Error(`Upstream ${res.status} for ${url}`);
164
- if (attempt < retries) {
165
- await sleep(wait);
166
- continue;
167
- }
168
- throw lastErr;
169
- }
170
- if (!res.ok) {
171
- const err = new Error(`Upstream ${res.status} for ${url}`);
172
- err.code = "E_FETCH";
173
- throw err;
174
- }
175
- const json = await res.json();
176
- return {
177
- data: validate(json),
178
- etag: res.headers.get("ETag"),
179
- notModified: false
180
- };
181
- } catch (err) {
182
- clearTimeout(timer);
183
- if (err.code === "E_NOT_FOUND_UPSTREAM") throw err;
184
- if (err.name === "AbortError") {
185
- lastErr = new Error(`Timeout fetching ${url}`);
186
- } else {
187
- lastErr = err;
188
- }
189
- if (attempt < retries && (!err.code || err.code === "E_FETCH")) {
190
- if (err.code === "E_FETCH") throw err;
191
- await sleep(Math.min(500 * 2 ** attempt + Math.random() * 250, 8e3));
192
- continue;
193
- }
194
- throw lastErr;
195
- }
196
- }
197
- throw lastErr;
198
- }
199
- function fetchAllTerms(opts, fetchFn) {
200
- return fetchJson(
201
- `${BANANA_BASE}/term/all`,
202
- (u) => rawTermSchema.array().parse(u),
203
- opts,
204
- fetchFn
205
- );
206
- }
207
- function fetchCurrentTerm(opts, fetchFn) {
208
- return fetchJson(
209
- `${BANANA_BASE}/term/current`,
210
- (u) => rawTermSchema.parse(u),
211
- opts,
212
- fetchFn
213
- );
214
- }
215
- function fetchSections(term, opts, fetchFn) {
216
- return fetchJson(
217
- `${BANANA_BASE}/sections/${term}`,
218
- (u) => rawSectionsSchema.parse(u),
219
- opts,
220
- fetchFn
221
- );
222
- }
223
- function fetchOfferingHistory(term, opts, fetchFn) {
224
- return fetchJson(
225
- `${BANANA_BASE}/offering-history/${term}`,
226
- (u) => rawHistorySchema.parse(u),
227
- opts,
228
- fetchFn
229
- );
230
- }
231
- function fetchCourseAreas(opts, fetchFn) {
232
- return fetchJson(
233
- `${BANANA_BASE}/course-areas`,
234
- (u) => u.map((x) => ({
235
- area: String(x.area),
236
- description: String(x.description)
237
- })),
238
- opts,
239
- fetchFn
240
- );
241
- }
242
-
243
- export {
244
- rawSectionSchema,
245
- rawSectionsSchema,
246
- rawHistorySchema,
247
- sectionSchema,
248
- cacheMetaSchema,
249
- BANANA_BASE,
250
- USER_AGENT,
251
- fetchJson,
252
- fetchAllTerms,
253
- fetchCurrentTerm,
254
- fetchSections,
255
- fetchOfferingHistory,
256
- fetchCourseAreas
257
- };
@@ -1,21 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- BANANA_BASE,
4
- USER_AGENT,
5
- fetchAllTerms,
6
- fetchCourseAreas,
7
- fetchCurrentTerm,
8
- fetchJson,
9
- fetchOfferingHistory,
10
- fetchSections,
11
- } from "./chunk-3PCAZ5XS.js";
12
- export {
13
- BANANA_BASE,
14
- USER_AGENT,
15
- fetchAllTerms,
16
- fetchCourseAreas,
17
- fetchCurrentTerm,
18
- fetchJson,
19
- fetchOfferingHistory,
20
- fetchSections,
21
- };
@@ -1,21 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- BANANA_BASE,
4
- USER_AGENT,
5
- fetchAllTerms,
6
- fetchCourseAreas,
7
- fetchCurrentTerm,
8
- fetchJson,
9
- fetchOfferingHistory,
10
- fetchSections
11
- } from "./chunk-R2DX67JM.js";
12
- export {
13
- BANANA_BASE,
14
- USER_AGENT,
15
- fetchAllTerms,
16
- fetchCourseAreas,
17
- fetchCurrentTerm,
18
- fetchJson,
19
- fetchOfferingHistory,
20
- fetchSections
21
- };
@@ -1,21 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- BANANA_BASE,
4
- USER_AGENT,
5
- fetchAllTerms,
6
- fetchCourseAreas,
7
- fetchCurrentTerm,
8
- fetchJson,
9
- fetchOfferingHistory,
10
- fetchSections
11
- } from "./chunk-B3TGUWJT.js";
12
- export {
13
- BANANA_BASE,
14
- USER_AGENT,
15
- fetchAllTerms,
16
- fetchCourseAreas,
17
- fetchCurrentTerm,
18
- fetchJson,
19
- fetchOfferingHistory,
20
- fetchSections
21
- };