@superbuilders/primer-tives 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/package.json +1 -1
  2. package/dist/client/choice-state.d.ts +0 -8
  3. package/dist/client/choice-state.d.ts.map +0 -1
  4. package/dist/client/consumed.d.ts +0 -3
  5. package/dist/client/consumed.d.ts.map +0 -1
  6. package/dist/client/content.d.ts +0 -20
  7. package/dist/client/content.d.ts.map +0 -1
  8. package/dist/client/create.d.ts +0 -20
  9. package/dist/client/create.d.ts.map +0 -1
  10. package/dist/client/extended-text-state.d.ts +0 -8
  11. package/dist/client/extended-text-state.d.ts.map +0 -1
  12. package/dist/client/feedback-state.d.ts +0 -7
  13. package/dist/client/feedback-state.d.ts.map +0 -1
  14. package/dist/client/index.d.ts +0 -11
  15. package/dist/client/index.d.ts.map +0 -1
  16. package/dist/client/index.js +0 -1085
  17. package/dist/client/index.js.map +0 -26
  18. package/dist/client/match-state.d.ts +0 -8
  19. package/dist/client/match-state.d.ts.map +0 -1
  20. package/dist/client/observation-state.d.ts +0 -6
  21. package/dist/client/observation-state.d.ts.map +0 -1
  22. package/dist/client/order-state.d.ts +0 -8
  23. package/dist/client/order-state.d.ts.map +0 -1
  24. package/dist/client/pci-state.d.ts +0 -6
  25. package/dist/client/pci-state.d.ts.map +0 -1
  26. package/dist/client/pci.d.ts +0 -38
  27. package/dist/client/pci.d.ts.map +0 -1
  28. package/dist/client/session-context.d.ts +0 -19
  29. package/dist/client/session-context.d.ts.map +0 -1
  30. package/dist/client/session.d.ts +0 -18
  31. package/dist/client/session.d.ts.map +0 -1
  32. package/dist/client/text-entry-state.d.ts +0 -8
  33. package/dist/client/text-entry-state.d.ts.map +0 -1
  34. package/dist/client/transport.d.ts +0 -46
  35. package/dist/client/transport.d.ts.map +0 -1
  36. package/dist/client/types.d.ts +0 -194
  37. package/dist/client/types.d.ts.map +0 -1
  38. package/dist/contracts/index.d.ts +0 -4
  39. package/dist/contracts/index.d.ts.map +0 -1
  40. package/dist/contracts/index.js +0 -305
  41. package/dist/contracts/index.js.map +0 -11
  42. package/dist/contracts/pci-schemas.d.ts +0 -25
  43. package/dist/contracts/pci-schemas.d.ts.map +0 -1
  44. package/dist/contracts/types.d.ts +0 -118
  45. package/dist/contracts/types.d.ts.map +0 -1
  46. package/dist/contracts/validation.d.ts +0 -132
  47. package/dist/contracts/validation.d.ts.map +0 -1
  48. package/dist/errors.d.ts +0 -23
  49. package/dist/errors.d.ts.map +0 -1
  50. package/dist/errors.js +0 -48
  51. package/dist/errors.js.map +0 -10
  52. package/dist/grade-level.d.ts +0 -5
  53. package/dist/grade-level.d.ts.map +0 -1
  54. package/dist/logger.d.ts +0 -8
  55. package/dist/logger.d.ts.map +0 -1
  56. package/dist/server/create-server.d.ts +0 -44
  57. package/dist/server/create-server.d.ts.map +0 -1
  58. package/dist/server/exchange.d.ts +0 -22
  59. package/dist/server/exchange.d.ts.map +0 -1
  60. package/dist/server/hints.d.ts +0 -25
  61. package/dist/server/hints.d.ts.map +0 -1
  62. package/dist/server/index.d.ts +0 -9
  63. package/dist/server/index.d.ts.map +0 -1
  64. package/dist/server/index.js +0 -531
  65. package/dist/server/index.js.map +0 -15
  66. package/dist/server/students.d.ts +0 -12
  67. package/dist/server/students.d.ts.map +0 -1
  68. package/dist/subject.d.ts +0 -6
  69. package/dist/subject.d.ts.map +0 -1
@@ -1,1085 +0,0 @@
1
- // src/errors.ts
2
- import * as errors from "@superbuilders/errors";
3
- var ErrNetwork = errors.new("network");
4
- var ErrJsonParse = errors.new("json parse");
5
- var ErrUnsupportedPci = errors.new("unsupported pci");
6
- var ErrInvalidAccessToken = errors.new("invalid access token");
7
- var ErrMalformedAccessToken = errors.new("malformed access token");
8
- var ErrTokenExpired = errors.new("access token expired");
9
- var ErrBadRequest = errors.new("bad request");
10
- var ErrServerError = errors.new("server error");
11
- var ErrTimeout = errors.new("timeout");
12
- var ErrForbidden = errors.new("forbidden");
13
- var ErrNotFound = errors.new("not found");
14
- var ErrConflict = errors.new("conflict");
15
- var ErrExternalAuthorityRequired = errors.new("external authority required");
16
- var ErrRateLimited = errors.new("rate limited");
17
- var ErrServiceUnavailable = errors.new("service unavailable");
18
- var ErrNotSerializable = errors.new("PrimerState is live in-memory state and must not be serialized or stored");
19
- var ErrInvalidSubmission = errors.new("invalid submission");
20
- var ErrInvalidSecretKey = errors.new("invalid secret key");
21
- var ErrStudentNotFound = errors.new("student not found");
22
- var ErrUnsupportedGrade = errors.new("unsupported grade");
23
- var ErrTimebackUnavailable = errors.new("timeback unavailable");
24
- // src/contracts/validation.ts
25
- import { z as z2 } from "zod";
26
-
27
- // src/contracts/pci-schemas.ts
28
- import { z } from "zod";
29
- var DivisionRemainderPropsSchema = z.object({
30
- dividend: z.number(),
31
- divisor: z.number()
32
- });
33
- var DivisionRemainderSubmissionSchema = z.object({
34
- quotient: z.string(),
35
- remainder: z.string()
36
- });
37
- var FractionOperandSchema = z.object({
38
- numerator: z.number(),
39
- denominator: z.number()
40
- });
41
- var FractionAdditionPropsSchema = z.object({
42
- left: FractionOperandSchema,
43
- right: FractionOperandSchema
44
- });
45
- var FractionAdditionSubmissionSchema = z.object({
46
- numerator: z.string(),
47
- denominator: z.string()
48
- });
49
-
50
- // src/contracts/validation.ts
51
- var MatchPairSchema = z2.object({
52
- source: z2.string(),
53
- target: z2.string()
54
- });
55
- var ChoiceSubmissionSchema = z2.object({
56
- type: z2.literal("choice"),
57
- selectedKeys: z2.array(z2.string())
58
- });
59
- var TextEntrySubmissionSchema = z2.object({
60
- type: z2.literal("text-entry"),
61
- value: z2.string()
62
- });
63
- var ExtendedTextSubmissionSchema = z2.object({
64
- type: z2.literal("extended-text"),
65
- values: z2.array(z2.string()).min(1)
66
- });
67
- var OrderSubmissionSchema = z2.object({
68
- type: z2.literal("order"),
69
- orderedKeys: z2.array(z2.string())
70
- });
71
- var MatchSubmissionSchema = z2.object({
72
- type: z2.literal("match"),
73
- pairs: z2.array(MatchPairSchema)
74
- });
75
- var DivisionRemainderPciSubmissionSchema = z2.object({
76
- type: z2.literal("portable-custom"),
77
- pciId: z2.literal("urn:primer:pci:division-remainder"),
78
- value: DivisionRemainderSubmissionSchema
79
- });
80
- var FractionAdditionPciSubmissionSchema = z2.object({
81
- type: z2.literal("portable-custom"),
82
- pciId: z2.literal("urn:primer:pci:fraction-addition"),
83
- value: FractionAdditionSubmissionSchema
84
- });
85
- var RendererSubmissionSchema = z2.union([
86
- ChoiceSubmissionSchema,
87
- TextEntrySubmissionSchema,
88
- ExtendedTextSubmissionSchema,
89
- OrderSubmissionSchema,
90
- MatchSubmissionSchema,
91
- DivisionRemainderPciSubmissionSchema,
92
- FractionAdditionPciSubmissionSchema
93
- ]);
94
- function invalid(issues) {
95
- return { ok: false, issues: Array.isArray(issues) ? issues : [issues] };
96
- }
97
- function valid(submission) {
98
- return {
99
- ok: true,
100
- value: submission
101
- };
102
- }
103
- function duplicates(values, keyOf) {
104
- const seen = new Set;
105
- const duplicated = new Set;
106
- for (const value of values) {
107
- const key = keyOf(value);
108
- if (seen.has(key)) {
109
- duplicated.add(key);
110
- continue;
111
- }
112
- seen.add(key);
113
- }
114
- return [...duplicated];
115
- }
116
- function findUnknownIds(values, choices) {
117
- const ids = new Set(choices.map(function getId(choice) {
118
- return choice.identifier;
119
- }));
120
- return values.filter(function isUnknown(value) {
121
- return !ids.has(value);
122
- });
123
- }
124
- function countByIdentifier(pairs, side) {
125
- const counts = new Map;
126
- for (const pair of pairs) {
127
- const key = pair[side];
128
- counts.set(key, (counts.get(key) ?? 0) + 1);
129
- }
130
- return counts;
131
- }
132
- function validateUsageBounds(choices, counts, side) {
133
- const issues = [];
134
- for (const choice of choices) {
135
- const count = counts.get(choice.identifier) ?? 0;
136
- if (choice.matchMax !== 0 && count > choice.matchMax) {
137
- issues.push(`${side} '${choice.identifier}' used ${count} times, max ${choice.matchMax}`);
138
- }
139
- if (count < choice.matchMin) {
140
- issues.push(`${side} '${choice.identifier}' used ${count} times, min ${choice.matchMin}`);
141
- }
142
- }
143
- return issues;
144
- }
145
- function pciSubmissionSchema(pciId) {
146
- switch (pciId) {
147
- case "urn:primer:pci:division-remainder":
148
- return DivisionRemainderSubmissionSchema;
149
- case "urn:primer:pci:fraction-addition":
150
- return FractionAdditionSubmissionSchema;
151
- }
152
- }
153
- function validateChoiceSubmission(interaction, submission) {
154
- if (submission.type !== "choice") {
155
- return invalid(`submission type '${submission.type}' does not match interaction type 'choice'`);
156
- }
157
- const issues = [];
158
- if (submission.selectedKeys.length < interaction.minChoices) {
159
- issues.push(`need at least ${interaction.minChoices} selections`);
160
- }
161
- if (submission.selectedKeys.length > interaction.maxChoices) {
162
- issues.push(`at most ${interaction.maxChoices} selections`);
163
- }
164
- const duplicateKeys = duplicates(submission.selectedKeys, function keyOf(value) {
165
- return value;
166
- });
167
- if (duplicateKeys.length > 0) {
168
- issues.push("duplicate selections");
169
- }
170
- const unknownKeys = findUnknownIds(submission.selectedKeys, interaction.options);
171
- if (unknownKeys.length > 0) {
172
- issues.push(`unknown options: ${unknownKeys.join(", ")}`);
173
- }
174
- if (issues.length > 0) {
175
- return invalid(issues);
176
- }
177
- return valid(submission);
178
- }
179
- function validateTextEntrySubmission(interaction, submission) {
180
- if (submission.type !== "text-entry") {
181
- return invalid(`submission type '${submission.type}' does not match interaction type 'text-entry'`);
182
- }
183
- return valid(submission);
184
- }
185
- function validateExtendedTextSubmission(interaction, submission) {
186
- if (submission.type !== "extended-text") {
187
- return invalid(`submission type '${submission.type}' does not match interaction type 'extended-text'`);
188
- }
189
- const issues = [];
190
- if (interaction.cardinality === "single") {
191
- if (submission.values.length !== 1) {
192
- issues.push("single-cardinality extended-text requires exactly one value");
193
- }
194
- } else {
195
- if (submission.values.length < interaction.minStrings) {
196
- issues.push(`need at least ${interaction.minStrings} values`);
197
- }
198
- if (submission.values.length > interaction.maxStrings) {
199
- issues.push(`at most ${interaction.maxStrings} values`);
200
- }
201
- const duplicateValues = duplicates(submission.values, function keyOf(value) {
202
- return value;
203
- });
204
- if (duplicateValues.length > 0) {
205
- issues.push("duplicate values are not allowed for multiple-cardinality extended-text");
206
- }
207
- }
208
- if (issues.length > 0) {
209
- return invalid(issues);
210
- }
211
- return valid(submission);
212
- }
213
- function validateOrderSubmission(interaction, submission) {
214
- if (submission.type !== "order") {
215
- return invalid(`submission type '${submission.type}' does not match interaction type 'order'`);
216
- }
217
- const issues = [];
218
- if (submission.orderedKeys.length < interaction.minChoices) {
219
- issues.push(`need at least ${interaction.minChoices} selections`);
220
- }
221
- if (submission.orderedKeys.length > interaction.maxChoices) {
222
- issues.push(`at most ${interaction.maxChoices} selections`);
223
- }
224
- const duplicateKeys = duplicates(submission.orderedKeys, function keyOf(value) {
225
- return value;
226
- });
227
- if (duplicateKeys.length > 0) {
228
- issues.push("duplicate selections");
229
- }
230
- const unknownKeys = findUnknownIds(submission.orderedKeys, interaction.choices);
231
- if (unknownKeys.length > 0) {
232
- issues.push(`unknown choices: ${unknownKeys.join(", ")}`);
233
- }
234
- if (issues.length > 0) {
235
- return invalid(issues);
236
- }
237
- return valid(submission);
238
- }
239
- function validateMatchSubmission(interaction, submission) {
240
- if (submission.type !== "match") {
241
- return invalid(`submission type '${submission.type}' does not match interaction type 'match'`);
242
- }
243
- const issues = [];
244
- if (submission.pairs.length < interaction.minAssociations) {
245
- issues.push(`need at least ${interaction.minAssociations} associations`);
246
- }
247
- if (submission.pairs.length > interaction.maxAssociations) {
248
- issues.push(`at most ${interaction.maxAssociations} associations`);
249
- }
250
- const duplicatePairs = duplicates(submission.pairs, function keyOf(pair) {
251
- return `${pair.source}->${pair.target}`;
252
- });
253
- if (duplicatePairs.length > 0) {
254
- issues.push("duplicate associations are not allowed");
255
- }
256
- const sourceIds = new Set(interaction.sourceChoices.map(function getId(choice) {
257
- return choice.identifier;
258
- }));
259
- const targetIds = new Set(interaction.targetChoices.map(function getId(choice) {
260
- return choice.identifier;
261
- }));
262
- for (const pair of submission.pairs) {
263
- if (!sourceIds.has(pair.source)) {
264
- issues.push(`unknown source '${pair.source}'`);
265
- }
266
- if (!targetIds.has(pair.target)) {
267
- issues.push(`unknown target '${pair.target}'`);
268
- }
269
- }
270
- const sourceCounts = countByIdentifier(submission.pairs, "source");
271
- const targetCounts = countByIdentifier(submission.pairs, "target");
272
- issues.push(...validateUsageBounds(interaction.sourceChoices, sourceCounts, "source"));
273
- issues.push(...validateUsageBounds(interaction.targetChoices, targetCounts, "target"));
274
- if (issues.length > 0) {
275
- return invalid(issues);
276
- }
277
- return valid(submission);
278
- }
279
- function validatePortableCustomSubmission(interaction, submission) {
280
- if (submission.type !== "portable-custom") {
281
- return invalid(`submission type '${submission.type}' does not match interaction type 'portable-custom'`);
282
- }
283
- if (submission.pciId !== interaction.pciId) {
284
- return invalid(`submission PCI '${submission.pciId}' does not match interaction PCI '${interaction.pciId}'`);
285
- }
286
- const schema = pciSubmissionSchema(interaction.pciId);
287
- const result = schema.safeParse(submission.value);
288
- if (!result.success) {
289
- return invalid(result.error.issues.map(function toIssue(issue) {
290
- return issue.message;
291
- }));
292
- }
293
- return valid(submission);
294
- }
295
- function validateSubmissionForInteraction(interaction, submission) {
296
- switch (interaction.type) {
297
- case "choice":
298
- return validateChoiceSubmission(interaction, submission);
299
- case "text-entry":
300
- return validateTextEntrySubmission(interaction, submission);
301
- case "extended-text":
302
- return validateExtendedTextSubmission(interaction, submission);
303
- case "order":
304
- return validateOrderSubmission(interaction, submission);
305
- case "match":
306
- return validateMatchSubmission(interaction, submission);
307
- case "portable-custom":
308
- return validatePortableCustomSubmission(interaction, submission);
309
- }
310
- }
311
- function submissionValidationMessage(result) {
312
- return result.issues.join("; ");
313
- }
314
- // src/client/create.ts
315
- import * as errors10 from "@superbuilders/errors";
316
-
317
- // src/client/transport.ts
318
- import * as errors2 from "@superbuilders/errors";
319
- var ADVANCE_PATH = "/api/v0/advance";
320
- function parseAdvanceErrorBody(body) {
321
- if (body.length === 0) {
322
- return null;
323
- }
324
- const parsed = errors2.trySync(function parseJson() {
325
- return JSON.parse(body);
326
- });
327
- if (parsed.error) {
328
- return null;
329
- }
330
- return parsed.data;
331
- }
332
- function httpSentinel(status, body) {
333
- if (status === 400) {
334
- return ErrBadRequest;
335
- }
336
- if (status === 401) {
337
- const parsed = parseAdvanceErrorBody(body);
338
- if (parsed?.detail === "token_expired") {
339
- return ErrTokenExpired;
340
- }
341
- return ErrInvalidAccessToken;
342
- }
343
- if (status === 403) {
344
- return ErrForbidden;
345
- }
346
- if (status === 404) {
347
- return ErrNotFound;
348
- }
349
- if (status === 409) {
350
- return ErrConflict;
351
- }
352
- if (status === 429) {
353
- return ErrRateLimited;
354
- }
355
- if (status === 502 || status === 503 || status === 504) {
356
- return ErrServiceUnavailable;
357
- }
358
- return ErrServerError;
359
- }
360
- function isAbortError(err) {
361
- if (err instanceof DOMException && err.name === "AbortError") {
362
- return true;
363
- }
364
- if (err instanceof DOMException && err.name === "TimeoutError") {
365
- return true;
366
- }
367
- return false;
368
- }
369
- function createTransport(tc) {
370
- const fetchFn = tc.fetch ? tc.fetch : globalThis.fetch;
371
- const log = tc.log;
372
- function transportSignal() {
373
- if (tc.abort) {
374
- return tc.abort.signal;
375
- }
376
- return;
377
- }
378
- async function transport(body) {
379
- log?.debug("transport request", {
380
- intentKind: body.intent.kind,
381
- subject: body.subject
382
- });
383
- const url = `${tc.origin}${ADVANCE_PATH}`;
384
- const fetchResult = await fetchFn(url, {
385
- method: "POST",
386
- headers: {
387
- "Content-Type": "application/json",
388
- Authorization: `Bearer ${tc.accessToken}`
389
- },
390
- body: JSON.stringify(body),
391
- signal: transportSignal()
392
- }).then(function ok(r) {
393
- return { ok: true, response: r };
394
- }, function fail(err) {
395
- return { ok: false, error: err };
396
- });
397
- if (!fetchResult.ok) {
398
- if (isAbortError(fetchResult.error)) {
399
- log?.error("transport timeout", {
400
- intentKind: body.intent.kind
401
- });
402
- return { ok: false, error: errors2.wrap(ErrTimeout, fetchResult.error.message) };
403
- }
404
- log?.error("transport network error", {
405
- error: fetchResult.error
406
- });
407
- return { ok: false, error: errors2.wrap(ErrNetwork, fetchResult.error.message) };
408
- }
409
- const res = fetchResult.response;
410
- if (!res.ok) {
411
- const text = await res.text().catch(function fallback() {
412
- return "";
413
- });
414
- const sentinel = res.status === 422 ? ErrUnsupportedPci : httpSentinel(res.status, text);
415
- log?.error("transport http error", {
416
- status: res.status
417
- });
418
- return { ok: false, error: errors2.wrap(sentinel, text) };
419
- }
420
- const jsonResult = await res.json().then(function ok(data) {
421
- return { ok: true, data };
422
- }, function fail(err) {
423
- return { ok: false, error: err };
424
- });
425
- if (!jsonResult.ok) {
426
- log?.error("transport json parse failed", {
427
- error: jsonResult.error
428
- });
429
- return { ok: false, error: errors2.wrap(ErrJsonParse, jsonResult.error.message) };
430
- }
431
- log?.debug("transport success", {
432
- intentKind: body.intent.kind
433
- });
434
- return { ok: true, data: jsonResult.data };
435
- }
436
- return transport;
437
- }
438
-
439
- // src/client/session.ts
440
- import * as errors9 from "@superbuilders/errors";
441
-
442
- // src/client/consumed.ts
443
- function poisonToJSON() {
444
- throw ErrNotSerializable;
445
- }
446
-
447
- // src/client/choice-state.ts
448
- import * as errors3 from "@superbuilders/errors";
449
- function choiceState(ctx, stimulus, interaction, options, maxChoices, minChoices) {
450
- let submitPending;
451
- let submitKey;
452
- let timeoutPending;
453
- function beginSubmit(selectedKeys) {
454
- const submission = { type: "choice", selectedKeys };
455
- const key = JSON.stringify(submission);
456
- if (timeoutPending) {
457
- return Promise.resolve(ctx.errored(errors3.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
458
- }
459
- if (submitPending) {
460
- if (submitKey === key) {
461
- return submitPending;
462
- }
463
- return Promise.resolve(ctx.errored(errors3.wrap(ErrConflict, "cannot submit a different choice payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
464
- }
465
- const validation = validateSubmissionForInteraction(interaction, submission);
466
- if (!validation.ok) {
467
- ctx.log?.error("choice submit invalid", { selectedKeys, issues: validation.issues });
468
- return Promise.resolve(ctx.errored(errors3.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
469
- }
470
- submitKey = key;
471
- submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
472
- submitPending = undefined;
473
- submitKey = undefined;
474
- });
475
- return submitPending;
476
- }
477
- function beginTimeout() {
478
- const intent = { kind: "timeout" };
479
- if (submitPending) {
480
- return Promise.resolve(ctx.errored(errors3.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
481
- }
482
- if (timeoutPending) {
483
- return timeoutPending;
484
- }
485
- timeoutPending = ctx.execute(intent, "timeout").finally(function clearPending() {
486
- timeoutPending = undefined;
487
- });
488
- return timeoutPending;
489
- }
490
- return {
491
- phase: "interaction",
492
- kind: "choice",
493
- stimulus,
494
- interaction,
495
- options,
496
- maxChoices,
497
- minChoices,
498
- submitChoice: beginSubmit,
499
- timeout: beginTimeout,
500
- toJSON: poisonToJSON
501
- };
502
- }
503
-
504
- // src/client/extended-text-state.ts
505
- import * as errors4 from "@superbuilders/errors";
506
- function extendedTextState(ctx, stimulus, interaction) {
507
- if (interaction.cardinality === "single") {
508
- let submitText = function(value) {
509
- const submission = { type: "extended-text", values: [value] };
510
- const key = JSON.stringify(submission);
511
- if (timeoutPending2) {
512
- return Promise.resolve(ctx.errored(errors4.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
513
- }
514
- if (submitPending2) {
515
- if (submitKey2 === key) {
516
- return submitPending2;
517
- }
518
- return Promise.resolve(ctx.errored(errors4.wrap(ErrConflict, "cannot submit a different extended-text payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
519
- }
520
- const validation = validateSubmissionForInteraction(interaction, submission);
521
- if (!validation.ok) {
522
- ctx.log?.error("extended-text submit invalid", { value, issues: validation.issues });
523
- return Promise.resolve(ctx.errored(errors4.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
524
- }
525
- submitKey2 = key;
526
- submitPending2 = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
527
- submitPending2 = undefined;
528
- submitKey2 = undefined;
529
- });
530
- return submitPending2;
531
- }, timeout2 = function() {
532
- const intent = { kind: "timeout" };
533
- if (submitPending2) {
534
- return Promise.resolve(ctx.errored(errors4.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
535
- }
536
- if (timeoutPending2) {
537
- return timeoutPending2;
538
- }
539
- timeoutPending2 = ctx.execute(intent, "timeout").finally(function clearPending() {
540
- timeoutPending2 = undefined;
541
- });
542
- return timeoutPending2;
543
- };
544
- let submitPending2;
545
- let submitKey2;
546
- let timeoutPending2;
547
- return {
548
- phase: "interaction",
549
- kind: "extended-text",
550
- cardinality: "single",
551
- stimulus,
552
- interaction,
553
- submitText,
554
- timeout: timeout2,
555
- toJSON: poisonToJSON
556
- };
557
- }
558
- const multi = interaction;
559
- let submitPending;
560
- let submitKey;
561
- let timeoutPending;
562
- function submitTexts(values) {
563
- const submission = { type: "extended-text", values };
564
- const key = JSON.stringify(submission);
565
- if (timeoutPending) {
566
- return Promise.resolve(ctx.errored(errors4.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
567
- }
568
- if (submitPending) {
569
- if (submitKey === key) {
570
- return submitPending;
571
- }
572
- return Promise.resolve(ctx.errored(errors4.wrap(ErrConflict, "cannot submit a different extended-text payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
573
- }
574
- const validation = validateSubmissionForInteraction(multi, submission);
575
- if (!validation.ok) {
576
- ctx.log?.error("extended-text submit invalid", { values, issues: validation.issues });
577
- return Promise.resolve(ctx.errored(errors4.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
578
- }
579
- submitKey = key;
580
- submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
581
- submitPending = undefined;
582
- submitKey = undefined;
583
- });
584
- return submitPending;
585
- }
586
- function timeout() {
587
- const intent = { kind: "timeout" };
588
- if (submitPending) {
589
- return Promise.resolve(ctx.errored(errors4.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
590
- }
591
- if (timeoutPending) {
592
- return timeoutPending;
593
- }
594
- timeoutPending = ctx.execute(intent, "timeout").finally(function clearPending() {
595
- timeoutPending = undefined;
596
- });
597
- return timeoutPending;
598
- }
599
- return {
600
- phase: "interaction",
601
- kind: "extended-text",
602
- cardinality: "multiple",
603
- stimulus,
604
- interaction: multi,
605
- maxStrings: multi.maxStrings,
606
- minStrings: multi.minStrings,
607
- submitTexts,
608
- timeout,
609
- toJSON: poisonToJSON
610
- };
611
- }
612
-
613
- // src/client/feedback-state.ts
614
- function feedbackState(ctx, stimulus, interaction, submission, isCorrect, feedbackContent, review) {
615
- let pending;
616
- return {
617
- phase: "feedback",
618
- stimulus,
619
- interaction,
620
- submission,
621
- isCorrect,
622
- feedbackContent,
623
- review,
624
- advance: function advance() {
625
- if (pending) {
626
- return pending;
627
- }
628
- pending = ctx.execute({ kind: "observation" }, "observation");
629
- return pending;
630
- },
631
- toJSON: poisonToJSON
632
- };
633
- }
634
-
635
- // src/client/match-state.ts
636
- import * as errors5 from "@superbuilders/errors";
637
- function matchState(ctx, stimulus, interaction) {
638
- let submitPending;
639
- let submitKey;
640
- let timeoutPending;
641
- function submitMatch(pairs) {
642
- const submission = { type: "match", pairs };
643
- const key = JSON.stringify(submission);
644
- if (timeoutPending) {
645
- return Promise.resolve(ctx.errored(errors5.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
646
- }
647
- if (submitPending) {
648
- if (submitKey === key) {
649
- return submitPending;
650
- }
651
- return Promise.resolve(ctx.errored(errors5.wrap(ErrConflict, "cannot submit a different match payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
652
- }
653
- const validation = validateSubmissionForInteraction(interaction, submission);
654
- if (!validation.ok) {
655
- ctx.log?.error("match submit invalid", { pairs, issues: validation.issues });
656
- return Promise.resolve(ctx.errored(errors5.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
657
- }
658
- submitKey = key;
659
- submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
660
- submitPending = undefined;
661
- submitKey = undefined;
662
- });
663
- return submitPending;
664
- }
665
- function timeout() {
666
- const intent = { kind: "timeout" };
667
- if (submitPending) {
668
- return Promise.resolve(ctx.errored(errors5.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
669
- }
670
- if (timeoutPending) {
671
- return timeoutPending;
672
- }
673
- timeoutPending = ctx.execute(intent, "timeout").finally(function clearPending() {
674
- timeoutPending = undefined;
675
- });
676
- return timeoutPending;
677
- }
678
- return {
679
- phase: "interaction",
680
- kind: "match",
681
- stimulus,
682
- interaction,
683
- sourceChoices: interaction.sourceChoices,
684
- targetChoices: interaction.targetChoices,
685
- minAssociations: interaction.minAssociations,
686
- maxAssociations: interaction.maxAssociations,
687
- submitMatch,
688
- timeout,
689
- toJSON: poisonToJSON
690
- };
691
- }
692
-
693
- // src/client/observation-state.ts
694
- function observationState(ctx, stimulus) {
695
- let pending;
696
- return {
697
- phase: "observation",
698
- stimulus,
699
- advance: function advance() {
700
- if (pending) {
701
- return pending;
702
- }
703
- pending = ctx.execute({ kind: "observation" }, "observation");
704
- return pending;
705
- },
706
- toJSON: poisonToJSON
707
- };
708
- }
709
-
710
- // src/client/order-state.ts
711
- import * as errors6 from "@superbuilders/errors";
712
- function orderState(ctx, stimulus, interaction) {
713
- let submitPending;
714
- let submitKey;
715
- let timeoutPending;
716
- function submitOrder(orderedKeys) {
717
- const submission = { type: "order", orderedKeys };
718
- const key = JSON.stringify(submission);
719
- if (timeoutPending) {
720
- return Promise.resolve(ctx.errored(errors6.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
721
- }
722
- if (submitPending) {
723
- if (submitKey === key) {
724
- return submitPending;
725
- }
726
- return Promise.resolve(ctx.errored(errors6.wrap(ErrConflict, "cannot submit a different order payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
727
- }
728
- const validation = validateSubmissionForInteraction(interaction, submission);
729
- if (!validation.ok) {
730
- ctx.log?.error("order submit invalid", { orderedKeys, issues: validation.issues });
731
- return Promise.resolve(ctx.errored(errors6.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
732
- }
733
- submitKey = key;
734
- submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
735
- submitPending = undefined;
736
- submitKey = undefined;
737
- });
738
- return submitPending;
739
- }
740
- function timeout() {
741
- const intent = { kind: "timeout" };
742
- if (submitPending) {
743
- return Promise.resolve(ctx.errored(errors6.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
744
- }
745
- if (timeoutPending) {
746
- return timeoutPending;
747
- }
748
- timeoutPending = ctx.execute(intent, "timeout").finally(function clearPending() {
749
- timeoutPending = undefined;
750
- });
751
- return timeoutPending;
752
- }
753
- return {
754
- phase: "interaction",
755
- kind: "order",
756
- stimulus,
757
- interaction,
758
- choices: interaction.choices,
759
- minChoices: interaction.minChoices,
760
- maxChoices: interaction.maxChoices,
761
- submitOrder,
762
- timeout,
763
- toJSON: poisonToJSON
764
- };
765
- }
766
-
767
- // src/client/pci-state.ts
768
- import * as errors7 from "@superbuilders/errors";
769
- function pciInteractionState(ctx, stimulus, interaction) {
770
- let submitPending;
771
- let submitKey;
772
- let timeoutPending;
773
- const { pciId, properties } = interaction;
774
- function submit(value) {
775
- const submission = { type: "portable-custom", pciId, value };
776
- const key = JSON.stringify(submission);
777
- if (timeoutPending) {
778
- return Promise.resolve(ctx.errored(errors7.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
779
- }
780
- if (submitPending) {
781
- if (submitKey === key) {
782
- return submitPending;
783
- }
784
- return Promise.resolve(ctx.errored(errors7.wrap(ErrConflict, "cannot submit a different pci payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
785
- }
786
- ctx.log?.debug("pci submit", { pciId });
787
- submitKey = key;
788
- submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
789
- submitPending = undefined;
790
- submitKey = undefined;
791
- });
792
- return submitPending;
793
- }
794
- function timeout() {
795
- const intent = { kind: "timeout" };
796
- if (submitPending) {
797
- return Promise.resolve(ctx.errored(errors7.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
798
- }
799
- if (timeoutPending) {
800
- return timeoutPending;
801
- }
802
- ctx.log?.debug("pci timeout", { pciId });
803
- timeoutPending = ctx.execute(intent, "timeout").finally(function clearPending() {
804
- timeoutPending = undefined;
805
- });
806
- return timeoutPending;
807
- }
808
- return {
809
- phase: "interaction",
810
- kind: "portable-custom",
811
- stimulus,
812
- interaction,
813
- pciId,
814
- properties,
815
- submit,
816
- timeout,
817
- toJSON: poisonToJSON
818
- };
819
- }
820
-
821
- // src/client/text-entry-state.ts
822
- import * as errors8 from "@superbuilders/errors";
823
- function textEntryState(ctx, stimulus, interaction) {
824
- let submitPending;
825
- let submitKey;
826
- let timeoutPending;
827
- function submitText(value) {
828
- const submission = { type: "text-entry", value };
829
- const key = JSON.stringify(submission);
830
- if (timeoutPending) {
831
- return Promise.resolve(ctx.errored(errors8.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
832
- }
833
- if (submitPending) {
834
- if (submitKey === key) {
835
- return submitPending;
836
- }
837
- return Promise.resolve(ctx.errored(errors8.wrap(ErrConflict, "cannot submit a different text payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
838
- }
839
- const validation = validateSubmissionForInteraction(interaction, submission);
840
- if (!validation.ok) {
841
- ctx.log?.error("text-entry submit invalid", { value, issues: validation.issues });
842
- return Promise.resolve(ctx.errored(errors8.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
843
- }
844
- submitKey = key;
845
- submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
846
- submitPending = undefined;
847
- submitKey = undefined;
848
- });
849
- return submitPending;
850
- }
851
- function timeout() {
852
- const intent = { kind: "timeout" };
853
- if (submitPending) {
854
- return Promise.resolve(ctx.errored(errors8.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
855
- }
856
- if (timeoutPending) {
857
- return timeoutPending;
858
- }
859
- timeoutPending = ctx.execute(intent, "timeout").finally(function clearPending() {
860
- timeoutPending = undefined;
861
- });
862
- return timeoutPending;
863
- }
864
- return {
865
- phase: "interaction",
866
- kind: "text-entry",
867
- stimulus,
868
- interaction,
869
- submitText,
870
- timeout,
871
- toJSON: poisonToJSON
872
- };
873
- }
874
-
875
- // src/client/session.ts
876
- var FATAL_SENTINELS = [
877
- ErrBadRequest,
878
- ErrInvalidAccessToken,
879
- ErrTokenExpired,
880
- ErrForbidden,
881
- ErrNotFound,
882
- ErrUnsupportedPci
883
- ];
884
- function isFatalError(err) {
885
- for (const sentinel of FATAL_SENTINELS) {
886
- if (errors9.is(err, sentinel)) {
887
- return true;
888
- }
889
- }
890
- return false;
891
- }
892
- function isRetriableError(err) {
893
- return !errors9.is(err, ErrInvalidSubmission);
894
- }
895
- function makeSession(sc) {
896
- const log = sc.log;
897
- function resolve(result) {
898
- switch (result.outcome) {
899
- case "advanced":
900
- return fromAdvanced(result.stimulus, result.interaction);
901
- case "submitted":
902
- return feedbackState(ctx, result.stimulus, result.interaction, result.submission, result.isCorrect, result.feedbackContent, result.review);
903
- case "completed":
904
- return { phase: "completed", toJSON: poisonToJSON };
905
- }
906
- }
907
- function errored(error, failedPhase, intent) {
908
- let pending;
909
- const retriable = isRetriableError(error);
910
- const state = {
911
- phase: "errored",
912
- error,
913
- retriable,
914
- retry: function retry() {
915
- if (!retriable) {
916
- log?.debug("retry ignored for non-retriable error", { failedPhase });
917
- return Promise.resolve(state);
918
- }
919
- if (pending) {
920
- return pending;
921
- }
922
- log?.debug("retrying from errored state", { failedPhase });
923
- pending = execute(intent, failedPhase);
924
- return pending;
925
- },
926
- toJSON: poisonToJSON
927
- };
928
- return state;
929
- }
930
- async function execute(intent, phase) {
931
- const body = {
932
- supportedPcis: sc.supportedPcis,
933
- intent,
934
- subject: sc.subject
935
- };
936
- const result = await sc.transport(body);
937
- if (!result.ok) {
938
- if (isFatalError(result.error)) {
939
- log?.error("fatal transport error", { error: result.error, phase });
940
- return { phase: "fatal", error: result.error, retriable: false, toJSON: poisonToJSON };
941
- }
942
- return errored(result.error, phase, intent);
943
- }
944
- return resolve(result.data);
945
- }
946
- function isPciSupported(pciId) {
947
- for (const id of sc.supportedPcis) {
948
- if (id === pciId) {
949
- return true;
950
- }
951
- }
952
- return false;
953
- }
954
- function fromAdvanced(stimulus, interaction) {
955
- if (interaction === null) {
956
- return observationState(ctx, stimulus);
957
- }
958
- if (interaction.type === "portable-custom") {
959
- if (!isPciSupported(interaction.pciId)) {
960
- log?.error("unsupported pci in frame", { pciId: interaction.pciId });
961
- return {
962
- phase: "fatal",
963
- error: errors9.wrap(ErrUnsupportedPci, `pci '${interaction.pciId}'`),
964
- retriable: false,
965
- toJSON: poisonToJSON
966
- };
967
- }
968
- }
969
- return pendingInteractionState(stimulus, interaction);
970
- }
971
- function pendingInteractionState(stimulus, interaction) {
972
- switch (interaction.type) {
973
- case "choice":
974
- return choiceState(ctx, stimulus, interaction, interaction.options, interaction.maxChoices, interaction.minChoices);
975
- case "text-entry":
976
- return textEntryState(ctx, stimulus, interaction);
977
- case "extended-text":
978
- return extendedTextState(ctx, stimulus, interaction);
979
- case "order":
980
- return orderState(ctx, stimulus, interaction);
981
- case "match":
982
- return matchState(ctx, stimulus, interaction);
983
- case "portable-custom":
984
- return pciInteractionState(ctx, stimulus, interaction);
985
- }
986
- }
987
- const ctx = { log, execute, errored };
988
- return { execute };
989
- }
990
-
991
- // src/client/create.ts
992
- var ACCESS_TOKEN_PREFIX = "eyJ";
993
- function isMalformedJws(token) {
994
- if (!token.startsWith(ACCESS_TOKEN_PREFIX)) {
995
- return true;
996
- }
997
- const dotCount = token.split(".").length - 1;
998
- return dotCount !== 2;
999
- }
1000
- function create(config) {
1001
- const log = config.logger;
1002
- if (isMalformedJws(config.accessToken)) {
1003
- log?.error("malformed access token", { prefix: ACCESS_TOKEN_PREFIX });
1004
- throw errors10.wrap(ErrMalformedAccessToken, `token must start with '${ACCESS_TOKEN_PREFIX}' and contain two dots`);
1005
- }
1006
- const transport = createTransport({
1007
- accessToken: config.accessToken,
1008
- supportedPcis: config.supportedPcis,
1009
- origin: config.origin,
1010
- fetch: config.fetch,
1011
- abort: config.abort,
1012
- log
1013
- });
1014
- let startPromise;
1015
- async function doStart() {
1016
- log?.debug("start", { subject: config.subject });
1017
- const s = makeSession({
1018
- supportedPcis: config.supportedPcis,
1019
- subject: config.subject,
1020
- log,
1021
- transport
1022
- });
1023
- return s.execute({ kind: "observation" }, "observation");
1024
- }
1025
- return {
1026
- start() {
1027
- if (startPromise) {
1028
- log?.debug("start already called");
1029
- return startPromise;
1030
- }
1031
- startPromise = doStart();
1032
- return startPromise;
1033
- }
1034
- };
1035
- }
1036
- // src/subject.ts
1037
- var SUBJECTS = ["math", "vocabulary", "science"];
1038
- // src/client/content.ts
1039
- function inlinesToPlainText(nodes) {
1040
- const parts = [];
1041
- for (const node of nodes) {
1042
- switch (node.type) {
1043
- case "text":
1044
- parts.push(node.value);
1045
- break;
1046
- case "italic":
1047
- parts.push(node.value);
1048
- break;
1049
- case "latex":
1050
- parts.push(node.value);
1051
- break;
1052
- }
1053
- }
1054
- return parts.join("");
1055
- }
1056
- function blocksToPlainText(blocks) {
1057
- return blocks.map(function blockText(block) {
1058
- return inlinesToPlainText(block.children);
1059
- }).join(`
1060
- `);
1061
- }
1062
- export {
1063
- inlinesToPlainText,
1064
- create,
1065
- blocksToPlainText,
1066
- SUBJECTS,
1067
- ErrUnsupportedPci,
1068
- ErrTokenExpired,
1069
- ErrTimeout,
1070
- ErrServiceUnavailable,
1071
- ErrServerError,
1072
- ErrRateLimited,
1073
- ErrNotSerializable,
1074
- ErrNotFound,
1075
- ErrNetwork,
1076
- ErrMalformedAccessToken,
1077
- ErrJsonParse,
1078
- ErrInvalidSubmission,
1079
- ErrInvalidAccessToken,
1080
- ErrForbidden,
1081
- ErrConflict,
1082
- ErrBadRequest
1083
- };
1084
-
1085
- //# debugId=BFF38D60446A9E1864756E2164756E21