dsh-diagnostic-tutor 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/LICENSE +21 -0
- package/README.md +658 -0
- package/cordis.patch.yml +23 -0
- package/lib/api.js +413 -0
- package/lib/api.js.map +1 -0
- package/lib/client.js +2029 -0
- package/lib/client.js.map +1 -0
- package/lib/contract.js +14 -0
- package/lib/contract.js.map +1 -0
- package/lib/diagnosis.js +224 -0
- package/lib/diagnosis.js.map +1 -0
- package/lib/handoff.js +194 -0
- package/lib/handoff.js.map +1 -0
- package/lib/index.js +186 -0
- package/lib/index.js.map +1 -0
- package/lib/lesson.js +285 -0
- package/lib/lesson.js.map +1 -0
- package/lib/prompt.js +96 -0
- package/lib/prompt.js.map +1 -0
- package/lib/state.js +500 -0
- package/lib/state.js.map +1 -0
- package/lib/tools.js +994 -0
- package/lib/tools.js.map +1 -0
- package/lib/trust-fence.js +101 -0
- package/lib/trust-fence.js.map +1 -0
- package/lib/types/api.d.ts +62 -0
- package/lib/types/api.d.ts.map +1 -0
- package/lib/types/contract.d.ts +147 -0
- package/lib/types/contract.d.ts.map +1 -0
- package/lib/types/diagnosis.d.ts +116 -0
- package/lib/types/diagnosis.d.ts.map +1 -0
- package/lib/types/handoff.d.ts +141 -0
- package/lib/types/handoff.d.ts.map +1 -0
- package/lib/types/index.d.ts +71 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/lesson.d.ts +295 -0
- package/lib/types/lesson.d.ts.map +1 -0
- package/lib/types/prompt.d.ts +85 -0
- package/lib/types/prompt.d.ts.map +1 -0
- package/lib/types/state.d.ts +627 -0
- package/lib/types/state.d.ts.map +1 -0
- package/lib/types/tools.d.ts +38 -0
- package/lib/types/tools.d.ts.map +1 -0
- package/lib/types/trust-fence.d.ts +53 -0
- package/lib/types/trust-fence.d.ts.map +1 -0
- package/lib/types/udt.d.ts +95 -0
- package/lib/types/udt.d.ts.map +1 -0
- package/lib/types/vocabulary.d.ts +162 -0
- package/lib/types/vocabulary.d.ts.map +1 -0
- package/lib/udt.js +141 -0
- package/lib/udt.js.map +1 -0
- package/lib/vocabulary.js +182 -0
- package/lib/vocabulary.js.map +1 -0
- package/package.json +104 -0
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# dsh-diagnostic-tutor — bundle patch layer.
|
|
2
|
+
#
|
|
3
|
+
# This single `insert` row is the whole contribution of this package to a DSH
|
|
4
|
+
# profile. Installing the bundle through the official CLI:
|
|
5
|
+
#
|
|
6
|
+
# dsh plugin --profile <name> add dsh-diagnostic-tutor
|
|
7
|
+
#
|
|
8
|
+
# appends `dsh-diagnostic-tutor` to `dsh.profile.bundles`, and this patch is
|
|
9
|
+
# then merged as one layer during profile boot.
|
|
10
|
+
#
|
|
11
|
+
# Requirements verified against the real loader:
|
|
12
|
+
# - the file MUST be a top-level YAML array (otherwise ConfigFileError);
|
|
13
|
+
# - `name` MUST be a bare module specifier so Node resolution finds the
|
|
14
|
+
# installed package (relative paths resolve beside the patch file);
|
|
15
|
+
# - `name` MUST equal package.json#name;
|
|
16
|
+
# - `id` is a stable composition key that later layers can override.
|
|
17
|
+
#
|
|
18
|
+
# There is deliberately no `config:` block yet. Every value a user might want
|
|
19
|
+
# to change will become a Schemastery `Config` field first, so that users can
|
|
20
|
+
# override it from their own profile patch layer without editing this package.
|
|
21
|
+
- insert:
|
|
22
|
+
- id: diagnostic-tutor
|
|
23
|
+
name: 'dsh-diagnostic-tutor'
|
package/lib/api.js
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The plugin's browser-facing HTTP surface.
|
|
3
|
+
*
|
|
4
|
+
* Three rules shape everything here:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Only what the UI needs.** The panel asks for an overview, one node's
|
|
7
|
+
* detail, and a lesson. There is no general query surface.
|
|
8
|
+
* 2. **Views, never storage internals.** The browser receives a projection —
|
|
9
|
+
* ids, titles, relations, states, evidence — and never a raw record, a
|
|
10
|
+
* domain handle, a file path, or anything that would let it reason about
|
|
11
|
+
* where state lives. `storageDomain` is not reachable from the browser,
|
|
12
|
+
* by construction: this module is the only thing that touches it.
|
|
13
|
+
* 3. **Every request passes the trust fence**, and every body is parsed and
|
|
14
|
+
* validated here. A rejected caller gets 403 and learns nothing.
|
|
15
|
+
*
|
|
16
|
+
* Registered lazily on `webServer`, because a profile without a web surface
|
|
17
|
+
* should lose the UI rather than fail to load the plugin.
|
|
18
|
+
*/
|
|
19
|
+
import { beginHandoff, handoffView, withFocusRecorded, withObserved, withPromptFailure, withPrompted, } from './handoff.js';
|
|
20
|
+
import { focusPromptText } from './prompt.js';
|
|
21
|
+
import { guarded } from './trust-fence.js';
|
|
22
|
+
/** Largest request body accepted, in bytes. */
|
|
23
|
+
export const MAX_BODY_BYTES = 64 * 1024;
|
|
24
|
+
/** Route prefix owned by this plugin. */
|
|
25
|
+
export const API_PREFIX = '/diagnostic-tutor/api';
|
|
26
|
+
/* -------------------------------------------------------------------------- */
|
|
27
|
+
/* Views — the browser never sees a storage record */
|
|
28
|
+
/* -------------------------------------------------------------------------- */
|
|
29
|
+
function nodeView(node) {
|
|
30
|
+
return {
|
|
31
|
+
id: node.id,
|
|
32
|
+
title: node.title,
|
|
33
|
+
relation: node.relation,
|
|
34
|
+
state: node.state,
|
|
35
|
+
parentId: node.parentId ?? null,
|
|
36
|
+
evidenceCount: node.evidence.length,
|
|
37
|
+
updatedAt: node.updatedAt,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function focusView(focus, node) {
|
|
41
|
+
return {
|
|
42
|
+
courseId: focus.courseId,
|
|
43
|
+
nodeId: focus.nodeId,
|
|
44
|
+
nodeTitle: node.title,
|
|
45
|
+
startedAt: focus.startedAt,
|
|
46
|
+
status: focus.status,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function courseView(course) {
|
|
50
|
+
return {
|
|
51
|
+
id: course.id,
|
|
52
|
+
title: course.title,
|
|
53
|
+
goal: course.goal,
|
|
54
|
+
status: course.status,
|
|
55
|
+
createdAt: course.createdAt,
|
|
56
|
+
updatedAt: course.updatedAt,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/* -------------------------------------------------------------------------- */
|
|
60
|
+
/* Transport helpers */
|
|
61
|
+
/* -------------------------------------------------------------------------- */
|
|
62
|
+
function sendJson(res, status, payload) {
|
|
63
|
+
res.statusCode = status;
|
|
64
|
+
res.setHeader('content-type', 'application/json; charset=utf-8');
|
|
65
|
+
// This surface is same-origin only and must never be cached or framed.
|
|
66
|
+
res.setHeader('cache-control', 'no-store');
|
|
67
|
+
res.setHeader('x-content-type-options', 'nosniff');
|
|
68
|
+
res.end(JSON.stringify(payload));
|
|
69
|
+
}
|
|
70
|
+
function fail(res, status, code, message) {
|
|
71
|
+
sendJson(res, status, { ok: false, error: message === undefined ? { code } : { code, message } });
|
|
72
|
+
}
|
|
73
|
+
/** Read and parse a JSON body, bounded and never trusted. */
|
|
74
|
+
async function readJsonBody(req) {
|
|
75
|
+
const chunks = [];
|
|
76
|
+
let size = 0;
|
|
77
|
+
for await (const chunk of req) {
|
|
78
|
+
const buffer = chunk;
|
|
79
|
+
size += buffer.length;
|
|
80
|
+
if (size > MAX_BODY_BYTES)
|
|
81
|
+
return undefined;
|
|
82
|
+
chunks.push(buffer);
|
|
83
|
+
}
|
|
84
|
+
if (size === 0)
|
|
85
|
+
return {};
|
|
86
|
+
try {
|
|
87
|
+
const parsed = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
88
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
89
|
+
return undefined;
|
|
90
|
+
return parsed;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** A required non-empty string field, or `undefined` when absent/malformed. */
|
|
97
|
+
function stringField(body, key) {
|
|
98
|
+
const value = body[key];
|
|
99
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
100
|
+
}
|
|
101
|
+
/* -------------------------------------------------------------------------- */
|
|
102
|
+
/* Handler */
|
|
103
|
+
/* -------------------------------------------------------------------------- */
|
|
104
|
+
/**
|
|
105
|
+
* Which course the panel should show: the learner's active goal, else the most
|
|
106
|
+
* recently updated one. Read-only — the panel never changes focus implicitly.
|
|
107
|
+
*/
|
|
108
|
+
function currentCourse(state) {
|
|
109
|
+
const activeId = state.readLearner().activeCourseId;
|
|
110
|
+
if (activeId !== undefined) {
|
|
111
|
+
const active = state.readCourse(activeId);
|
|
112
|
+
if (active !== undefined)
|
|
113
|
+
return active;
|
|
114
|
+
}
|
|
115
|
+
return state
|
|
116
|
+
.listCourses()
|
|
117
|
+
.slice()
|
|
118
|
+
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
119
|
+
}
|
|
120
|
+
/** The active focus for one course, joined with its node, as a view. */
|
|
121
|
+
function focusOf(state, courseId) {
|
|
122
|
+
const focus = state.readFocus(courseId);
|
|
123
|
+
if (focus === undefined || focus.status !== 'active')
|
|
124
|
+
return null;
|
|
125
|
+
const node = state.readNode(focus.nodeId);
|
|
126
|
+
if (node === undefined)
|
|
127
|
+
return null;
|
|
128
|
+
return focusView(focus, node);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The recommendation attached to the current focus, if it has one and the
|
|
132
|
+
* learner has not acted on it.
|
|
133
|
+
*
|
|
134
|
+
* "Attached to the focus" is the whole test: starting a new focus writes a
|
|
135
|
+
* fresh record without `nextStepId`, so acting on a recommendation clears it
|
|
136
|
+
* without anything having to remember that it did.
|
|
137
|
+
*/
|
|
138
|
+
function nextStepOf(state, courseId) {
|
|
139
|
+
const focus = state.readFocus(courseId);
|
|
140
|
+
if (focus?.nextStepId === undefined)
|
|
141
|
+
return null;
|
|
142
|
+
const step = state.readNextStep(focus.nextStepId);
|
|
143
|
+
if (step === undefined)
|
|
144
|
+
return null;
|
|
145
|
+
const from = state.readNode(step.fromNodeId);
|
|
146
|
+
const target = step.targetNodeId === undefined ? undefined : state.readNode(step.targetNodeId);
|
|
147
|
+
return {
|
|
148
|
+
fromNodeId: step.fromNodeId,
|
|
149
|
+
fromNodeTitle: from?.title ?? step.fromNodeId,
|
|
150
|
+
targetNodeId: step.targetNodeId ?? null,
|
|
151
|
+
targetNodeTitle: target?.title ?? null,
|
|
152
|
+
action: step.action,
|
|
153
|
+
reason: step.reason,
|
|
154
|
+
createdAt: step.createdAt,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function handleOverview(state, deps, res) {
|
|
158
|
+
const course = currentCourse(state);
|
|
159
|
+
if (course === undefined) {
|
|
160
|
+
// An empty state is a normal state, not an error: the panel says so. The
|
|
161
|
+
// shape must still be complete — an omitted field reads as `undefined`,
|
|
162
|
+
// and a surface that checks `teachingBrain === false` would never fire in
|
|
163
|
+
// the one state it exists for.
|
|
164
|
+
sendJson(res, 200, {
|
|
165
|
+
ok: true,
|
|
166
|
+
course: null,
|
|
167
|
+
nodes: [],
|
|
168
|
+
focus: null,
|
|
169
|
+
nextStep: null,
|
|
170
|
+
handoff: null,
|
|
171
|
+
teachingBrain: deps.teachingBrain ?? null,
|
|
172
|
+
lessonCount: 0,
|
|
173
|
+
});
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
sendJson(res, 200, {
|
|
177
|
+
ok: true,
|
|
178
|
+
course: courseView(course),
|
|
179
|
+
nodes: state.listNodes(course.id).map(nodeView),
|
|
180
|
+
focus: focusOf(state, course.id),
|
|
181
|
+
nextStep: nextStepOf(state, course.id),
|
|
182
|
+
teachingBrain: deps.teachingBrain ?? null,
|
|
183
|
+
// The handoff for whichever node is focused, so a reload or a restart
|
|
184
|
+
// rebuilds the progress line instead of showing a blank wait.
|
|
185
|
+
handoff: (() => {
|
|
186
|
+
const focus = state.readFocus(course.id);
|
|
187
|
+
return focus === undefined ? null : handoffFor(state, focus.nodeId, Date.now());
|
|
188
|
+
})(),
|
|
189
|
+
lessonCount: state.lessonCount(),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
/** `GET /lesson?nodeId=` — the lesson the tutor has written for a node, if any. */
|
|
193
|
+
function handleLesson(state, course, nodeId, res) {
|
|
194
|
+
if (course === undefined)
|
|
195
|
+
return fail(res, 404, 'no-course');
|
|
196
|
+
const node = state.readNode(nodeId);
|
|
197
|
+
if (node === undefined || node.courseId !== course.id)
|
|
198
|
+
return fail(res, 404, 'no-such-node');
|
|
199
|
+
sendJson(res, 200, { ok: true, lesson: state.lessonForNode(nodeId) ?? null });
|
|
200
|
+
}
|
|
201
|
+
function handleNode(state, course, nodeId, res) {
|
|
202
|
+
if (course === undefined)
|
|
203
|
+
return fail(res, 404, 'no-course');
|
|
204
|
+
const node = state.readNode(nodeId);
|
|
205
|
+
if (node === undefined || node.courseId !== course.id)
|
|
206
|
+
return fail(res, 404, 'no-such-node');
|
|
207
|
+
const parent = node.parentId === undefined ? undefined : state.readNode(node.parentId);
|
|
208
|
+
const children = state
|
|
209
|
+
.listNodes(course.id)
|
|
210
|
+
.filter((candidate) => candidate.parentId === node.id);
|
|
211
|
+
sendJson(res, 200, {
|
|
212
|
+
ok: true,
|
|
213
|
+
node: {
|
|
214
|
+
...nodeView(node),
|
|
215
|
+
// The evidence trail is the whole point of a diagnosis map, so it is
|
|
216
|
+
// included here and nowhere else.
|
|
217
|
+
evidence: node.evidence.map((entry) => ({
|
|
218
|
+
kind: entry.kind,
|
|
219
|
+
at: entry.at,
|
|
220
|
+
...(entry.note === undefined ? {} : { note: entry.note }),
|
|
221
|
+
...(entry.readiness === undefined ? {} : { readiness: entry.readiness }),
|
|
222
|
+
})),
|
|
223
|
+
},
|
|
224
|
+
parent: parent === undefined ? null : nodeView(parent),
|
|
225
|
+
children: children.map(nodeView),
|
|
226
|
+
lessonExists: state.lessonForNode(node.id) !== undefined,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
/** How long a handoff is left alone before another request is treated as a retry. */
|
|
230
|
+
const HANDOFF_DEDUPE_MS = 3000;
|
|
231
|
+
/** Build the handoff view, which is what a surface renders. */
|
|
232
|
+
function handoffFor(state, nodeId, nowMs) {
|
|
233
|
+
const record = state.readHandoff(nodeId);
|
|
234
|
+
if (record === undefined)
|
|
235
|
+
return null;
|
|
236
|
+
return handoffView(record, nowMs, state.lessonForNode(nodeId) !== undefined);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* `POST /focus { nodeId, sessionId? }` — what Start learning and Continue do.
|
|
240
|
+
*
|
|
241
|
+
* Three steps, in this order and deliberately:
|
|
242
|
+
*
|
|
243
|
+
* 1. **record the handoff and the focus** — this is the durable part, and it
|
|
244
|
+
* must exist even when no agent can be reached;
|
|
245
|
+
* 2. **wake the tutor** — a failure here is reported, never rolled back, since
|
|
246
|
+
* the focus is what both sides read;
|
|
247
|
+
* 3. **stamp the prompt** — so the timing chain has a start and the UI has
|
|
248
|
+
* something to say while the model runs.
|
|
249
|
+
*
|
|
250
|
+
* Idempotent by target node. A second request while the same handoff is fresh
|
|
251
|
+
* returns it untouched, which is what makes a double click harmless; a request
|
|
252
|
+
* after it has gone quiet is a retry and bumps the attempt count.
|
|
253
|
+
*/
|
|
254
|
+
async function handleStartFocus(state, deps, course, req, res) {
|
|
255
|
+
if (course === undefined)
|
|
256
|
+
return fail(res, 404, 'no-course');
|
|
257
|
+
const body = await readJsonBody(req);
|
|
258
|
+
if (body === undefined)
|
|
259
|
+
return fail(res, 400, 'bad-body');
|
|
260
|
+
const nodeId = stringField(body, 'nodeId');
|
|
261
|
+
if (nodeId === undefined)
|
|
262
|
+
return fail(res, 400, 'missing-node-id');
|
|
263
|
+
const sessionId = stringField(body, 'sessionId');
|
|
264
|
+
const node = state.readNode(nodeId);
|
|
265
|
+
if (node === undefined || node.courseId !== course.id)
|
|
266
|
+
return fail(res, 404, 'no-such-node');
|
|
267
|
+
const now = new Date();
|
|
268
|
+
const previous = state.readHandoff(nodeId);
|
|
269
|
+
// Double-click protection: the same target, still in flight and recent, is
|
|
270
|
+
// the same handoff. Not an error — just nothing new to do.
|
|
271
|
+
if (previous !== undefined &&
|
|
272
|
+
previous.status !== 'failed' &&
|
|
273
|
+
now.getTime() - Date.parse(previous.requestedAt) < HANDOFF_DEDUPE_MS) {
|
|
274
|
+
const focus = state.readFocus(course.id);
|
|
275
|
+
sendJson(res, 200, {
|
|
276
|
+
ok: true,
|
|
277
|
+
focus: focus === undefined ? null : focusView(focus, node),
|
|
278
|
+
prompted: previous.promptedAt !== undefined,
|
|
279
|
+
handoff: handoffFor(state, nodeId, now.getTime()),
|
|
280
|
+
deduped: true,
|
|
281
|
+
});
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
let handoff = beginHandoff({
|
|
285
|
+
courseId: course.id,
|
|
286
|
+
fromNodeId: previous?.fromNodeId ?? state.readFocus(course.id)?.nodeId ?? nodeId,
|
|
287
|
+
targetNodeId: nodeId,
|
|
288
|
+
now: now.toISOString(),
|
|
289
|
+
...(sessionId === undefined ? {} : { sessionId }),
|
|
290
|
+
...(previous === undefined ? {} : { previous }),
|
|
291
|
+
});
|
|
292
|
+
await state.writeHandoff(handoff);
|
|
293
|
+
let focus;
|
|
294
|
+
try {
|
|
295
|
+
focus = await state.startFocus(course.id, nodeId, now.toISOString());
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
// Covers an unknown node and a node from another course alike.
|
|
299
|
+
return fail(res, 404, 'no-such-node', error.message);
|
|
300
|
+
}
|
|
301
|
+
handoff = withFocusRecorded(handoff, new Date().toISOString());
|
|
302
|
+
await state.writeHandoff(handoff);
|
|
303
|
+
const outcome = deps.prompt(sessionId, focusPromptText(course, node));
|
|
304
|
+
handoff = outcome.prompted
|
|
305
|
+
? withPrompted(handoff, new Date().toISOString())
|
|
306
|
+
: withPromptFailure(handoff, new Date().toISOString(), outcome.reason ?? 'the tutor was not reached');
|
|
307
|
+
await state.writeHandoff(handoff);
|
|
308
|
+
sendJson(res, 200, {
|
|
309
|
+
ok: true,
|
|
310
|
+
focus: focusView(focus, node),
|
|
311
|
+
prompted: outcome.prompted,
|
|
312
|
+
...(outcome.reason === undefined ? {} : { promptReason: outcome.reason }),
|
|
313
|
+
handoff: handoffFor(state, nodeId, Date.now()),
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* `GET /export` — everything the learner owns, as one file.
|
|
318
|
+
*
|
|
319
|
+
* Served as an attachment with a dated filename so a browser saves it rather
|
|
320
|
+
* than rendering it. The document is self-describing (`format` + `version`), so
|
|
321
|
+
* it explains itself on a disk years from now without this plugin installed.
|
|
322
|
+
*/
|
|
323
|
+
function handleExport(state, res) {
|
|
324
|
+
const now = new Date();
|
|
325
|
+
const document = state.exportState(now.toISOString());
|
|
326
|
+
const body = JSON.stringify(document, null, 2);
|
|
327
|
+
res.setHeader('content-type', 'application/json; charset=utf-8');
|
|
328
|
+
res.setHeader('content-disposition', `attachment; filename="dsh-diagnostic-tutor-${now.toISOString().slice(0, 10)}.json"`);
|
|
329
|
+
res.setHeader('cache-control', 'no-store');
|
|
330
|
+
res.statusCode = 200;
|
|
331
|
+
res.end(body);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* `POST /reset` — delete everything and return to first run.
|
|
335
|
+
*
|
|
336
|
+
* Destructive and irreversible on purpose. An undo would mean keeping a copy of
|
|
337
|
+
* exactly what the learner asked to remove, which defeats the request. The
|
|
338
|
+
* surface asks twice; the route does as it is told.
|
|
339
|
+
*/
|
|
340
|
+
async function handleReset(state, res) {
|
|
341
|
+
await state.resetState(new Date().toISOString());
|
|
342
|
+
await state.ensureLearner(new Date().toISOString());
|
|
343
|
+
sendJson(res, 200, { ok: true, reset: true });
|
|
344
|
+
}
|
|
345
|
+
/** `POST /handoff/observed { nodeId }` — a surface has rendered the lesson. */
|
|
346
|
+
async function handleObserved(state, req, res) {
|
|
347
|
+
const body = await readJsonBody(req);
|
|
348
|
+
if (body === undefined)
|
|
349
|
+
return fail(res, 400, 'bad-body');
|
|
350
|
+
const nodeId = stringField(body, 'nodeId');
|
|
351
|
+
if (nodeId === undefined)
|
|
352
|
+
return fail(res, 400, 'missing-node-id');
|
|
353
|
+
const record = state.readHandoff(nodeId);
|
|
354
|
+
if (record === undefined)
|
|
355
|
+
return fail(res, 404, 'no-handoff');
|
|
356
|
+
await state.writeHandoff(withObserved(record, new Date().toISOString()));
|
|
357
|
+
sendJson(res, 200, { ok: true, handoff: handoffFor(state, nodeId, Date.now()) });
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Build the request handler.
|
|
361
|
+
*
|
|
362
|
+
* @param deps - the open state handle and the prompt hook.
|
|
363
|
+
* @returns a handler suitable for `webServer.register`.
|
|
364
|
+
*/
|
|
365
|
+
export function createApiHandler(deps) {
|
|
366
|
+
const { state } = deps;
|
|
367
|
+
return async (req, res) => {
|
|
368
|
+
const method = req.method ?? 'GET';
|
|
369
|
+
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
370
|
+
const route = url.pathname.slice(API_PREFIX.length) || '/';
|
|
371
|
+
const course = currentCourse(state);
|
|
372
|
+
if (method === 'GET' && route === '/overview')
|
|
373
|
+
return handleOverview(state, deps, res);
|
|
374
|
+
if (method === 'GET' && route === '/export')
|
|
375
|
+
return handleExport(state, res);
|
|
376
|
+
if (method === 'POST' && route === '/reset')
|
|
377
|
+
return await handleReset(state, res);
|
|
378
|
+
if (method === 'GET' && route === '/node') {
|
|
379
|
+
const nodeId = url.searchParams.get('id');
|
|
380
|
+
if (nodeId === null || nodeId.length === 0)
|
|
381
|
+
return fail(res, 400, 'missing-id');
|
|
382
|
+
return handleNode(state, course, nodeId, res);
|
|
383
|
+
}
|
|
384
|
+
if (method === 'GET' && route === '/lesson') {
|
|
385
|
+
const nodeId = url.searchParams.get('nodeId');
|
|
386
|
+
if (nodeId === null || nodeId.length === 0)
|
|
387
|
+
return fail(res, 400, 'missing-node-id');
|
|
388
|
+
return handleLesson(state, course, nodeId, res);
|
|
389
|
+
}
|
|
390
|
+
if (method === 'POST' && route === '/focus') {
|
|
391
|
+
return handleStartFocus(state, deps, course, req, res);
|
|
392
|
+
}
|
|
393
|
+
if (method === 'POST' && route === '/handoff/observed') {
|
|
394
|
+
return handleObserved(state, req, res);
|
|
395
|
+
}
|
|
396
|
+
return fail(res, method === 'GET' || method === 'POST' ? 404 : 405, 'no-such-route');
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Mount the API on the web server.
|
|
401
|
+
*
|
|
402
|
+
* @param server - the `webServer` service taken from `ctx`.
|
|
403
|
+
* @param deps - the open state handle and the prompt hook.
|
|
404
|
+
* @returns the route disposer.
|
|
405
|
+
*/
|
|
406
|
+
export function registerApi(server, deps) {
|
|
407
|
+
return server.register({
|
|
408
|
+
kind: 'prefix',
|
|
409
|
+
path: API_PREFIX,
|
|
410
|
+
handler: guarded(createApiHandler(deps)),
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
//# sourceMappingURL=api.js.map
|
package/lib/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|