@intellectif/lk-core 0.8.2 → 0.9.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +5 -4
  3. package/dist/{chunk-2M76F32Y.js → chunk-6DM2H6BD.js} +2 -2
  4. package/dist/{chunk-HCOWSGEZ.js → chunk-AHEUYOT3.js} +705 -17
  5. package/dist/chunk-AHEUYOT3.js.map +1 -0
  6. package/dist/{chunk-FKH4YT5X.cjs → chunk-CVRALNUK.cjs} +706 -18
  7. package/dist/chunk-CVRALNUK.cjs.map +1 -0
  8. package/dist/{chunk-NHGXOOZ2.js → chunk-ELTP5P6V.js} +2 -2
  9. package/dist/{chunk-YJZY5TPY.cjs → chunk-MOLL5KXO.cjs} +8 -8
  10. package/dist/{chunk-YJZY5TPY.cjs.map → chunk-MOLL5KXO.cjs.map} +1 -1
  11. package/dist/{chunk-LNA33IK7.js → chunk-QX7P3CHP.js} +2 -2
  12. package/dist/{chunk-7ACNBDSF.cjs → chunk-RL2PQLCY.cjs} +4 -4
  13. package/dist/{chunk-7ACNBDSF.cjs.map → chunk-RL2PQLCY.cjs.map} +1 -1
  14. package/dist/{chunk-FVLKIL6W.cjs → chunk-YVZCFUGP.cjs} +12 -12
  15. package/dist/{chunk-FVLKIL6W.cjs.map → chunk-YVZCFUGP.cjs.map} +1 -1
  16. package/dist/index.cjs +134 -42
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +157 -6
  19. package/dist/index.d.ts +157 -6
  20. package/dist/index.js +115 -23
  21. package/dist/index.js.map +1 -1
  22. package/dist/schemas.cjs +3 -3
  23. package/dist/schemas.js +2 -2
  24. package/dist/scoring.cjs +3 -3
  25. package/dist/scoring.js +2 -2
  26. package/dist/xapi.cjs +3 -3
  27. package/dist/xapi.js +2 -2
  28. package/package.json +1 -1
  29. package/dist/chunk-FKH4YT5X.cjs.map +0 -1
  30. package/dist/chunk-HCOWSGEZ.js.map +0 -1
  31. /package/dist/{chunk-2M76F32Y.js.map → chunk-6DM2H6BD.js.map} +0 -0
  32. /package/dist/{chunk-NHGXOOZ2.js.map → chunk-ELTP5P6V.js.map} +0 -0
  33. /package/dist/{chunk-LNA33IK7.js.map → chunk-QX7P3CHP.js.map} +0 -0
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ScoredItem } from './scoring.js';
2
2
  export { AssessmentScore, AssessmentSectionInput, Band, CompositionPolicy, DEFAULT_PASS_THRESHOLD, PassFailureReason, RoundingMode, RoundingPolicy, SectionScore, classifyBand, composeAssessmentScore, computePassThreshold, evaluate, gte, roundGrade, score } from './scoring.js';
3
- import { m as ItemOutcome, A as ActivityData, L as LearnerResponse, V as ValidationError, C as CriterionScore, G as GradeRecord, N as NativeControlHint, c as ActivityMedia, r as ScoringResult, D as DeferredScoringPartial, F as FillInTheBlanksData, f as FillInTheBlanksLearnerResponse, M as MultipleChoiceData, o as MultipleChoiceLearnerResponse, W as WrittenResponseData, u as WrittenResponseLearnerResponse } from './activity-CPtJUBek.js';
4
- export { a as ActivityDataMap, b as ActivityFeedback, d as ActivityResult, e as ActivityType, B as BlankConfig, g as Grader, h as GraderKind, i as GraderUsage, j as GradingState, I as InlineCorrection, k as InteractionEvent, l as InteractionKind, n as LearnerResponseMap, p as MultipleChoiceOption, S as ScoringDetail, q as ScoringOutcome, T as TextMatchPolicy, s as TextMatchResult, t as ValidationResult, v as WrittenResponseRubric, w as WrittenResponseRubricCriterion, X as XAPIActor, x as XAPIConfig, y as XAPIContext, z as XAPIContextActivities, E as XAPIError, H as XAPIObject, J as XAPIResult, K as XAPIScore, O as XAPIStatement, P as XAPIVerbObject, Q as levenshteinDistance, R as matchText } from './activity-CPtJUBek.js';
3
+ import { m as ItemOutcome, A as ActivityData, L as LearnerResponse, V as ValidationError, e as ActivityType, a as ActivityDataMap, C as CriterionScore, G as GradeRecord, N as NativeControlHint, c as ActivityMedia, r as ScoringResult, D as DeferredScoringPartial, F as FillInTheBlanksData, f as FillInTheBlanksLearnerResponse, M as MultipleChoiceData, o as MultipleChoiceLearnerResponse, W as WrittenResponseData, u as WrittenResponseLearnerResponse } from './activity-CPtJUBek.js';
4
+ export { b as ActivityFeedback, d as ActivityResult, B as BlankConfig, g as Grader, h as GraderKind, i as GraderUsage, j as GradingState, I as InlineCorrection, k as InteractionEvent, l as InteractionKind, n as LearnerResponseMap, p as MultipleChoiceOption, S as ScoringDetail, q as ScoringOutcome, T as TextMatchPolicy, s as TextMatchResult, t as ValidationResult, v as WrittenResponseRubric, w as WrittenResponseRubricCriterion, X as XAPIActor, x as XAPIConfig, y as XAPIContext, z as XAPIContextActivities, E as XAPIError, H as XAPIObject, J as XAPIResult, K as XAPIScore, O as XAPIStatement, P as XAPIVerbObject, Q as levenshteinDistance, R as matchText } from './activity-CPtJUBek.js';
5
5
  import { u as SequenceSlot, S as SequenceEntry, I as ItemGroup } from './index-BvrA8nIV.js';
6
6
  export { B as BlankConfigSchema, F as FeedbackSchema, a as FillInTheBlanksDataSchema, b as ItemGroupSchema, M as MediaPlaybackSchema, c as MediaSchema, d as MediaUrlSchema, e as MultipleChoiceDataSchema, f as MultipleChoiceOptionSchema, N as NativeControlHintSchema, R as RedactedActivity, g as RedactedBlankConfig, h as RedactedBlankConfigSchema, i as RedactedFillInTheBlanksData, j as RedactedFillInTheBlanksDataSchema, k as RedactedItemGroupSchema, l as RedactedMediaSchema, m as RedactedMultipleChoiceData, n as RedactedMultipleChoiceDataSchema, o as RedactedMultipleChoiceOption, p as RedactedMultipleChoiceOptionSchema, q as RedactedStimulus, r as RedactedStimulusSchema, s as RedactedWrittenResponseData, t as RedactedWrittenResponseDataSchema, v as SequenceSlotGroup, w as Stimulus, x as StimulusKind, y as StimulusSchema, T as TextMatchPolicySchema, W as WrittenResponseDataSchema, z as WrittenResponseRubricCriterionSchema, A as WrittenResponseRubricSchema, C as fillInTheBlanksJsonSchema, D as itemGroupJsonSchema, E as jsonSchemaFor, G as multipleChoiceJsonSchema, H as stimulusJsonSchema, J as validateActivity, K as validateItemGroup, L as writtenResponseJsonSchema } from './index-BvrA8nIV.js';
7
7
  import { z } from 'zod/v4';
@@ -327,6 +327,118 @@ declare function restoreAttemptState(plan: AttemptPlan, state: AttemptState): At
327
327
  */
328
328
  declare function diffResponses(before: Pick<AttemptState, 'responses'>, after: Pick<AttemptState, 'responses'>): ResponseDiffEntry[];
329
329
 
330
+ /**
331
+ * Why a draft is not an activity yet.
332
+ *
333
+ * - `incomplete` — something the author has not written yet: an empty title, an
334
+ * option with no text, no option marked correct. The fix is to add it.
335
+ * - `invalid` — something is wrong in a way that writing more cannot fix: two
336
+ * correct options on a single-selection question, a maximum word count below
337
+ * the minimum, a media address with a scheme the SDK refuses, a `null` in an
338
+ * optional field that accepts none. The fix is to change it.
339
+ */
340
+ type DraftSeverity = 'incomplete' | 'invalid';
341
+ /**
342
+ * One problem with a draft. A {@link ValidationError} with a severity, so code
343
+ * that already lists `validateActivity` errors can list these unchanged.
344
+ */
345
+ interface DraftIssue extends ValidationError {
346
+ severity: DraftSeverity;
347
+ }
348
+ /** A draft that is a valid activity and has nothing left to write. */
349
+ interface DraftComplete<T> {
350
+ status: 'complete';
351
+ /** The validated activity, exactly as `validateActivity` returns it. */
352
+ data: T;
353
+ /** Always empty. Present so that every result can be read the same way. */
354
+ issues: DraftIssue[];
355
+ }
356
+ /**
357
+ * A draft that is not an activity yet: `invalid` when any issue is invalid,
358
+ * otherwise `incomplete`. It carries no `data`, because nothing here may be
359
+ * stored or rendered as an activity.
360
+ */
361
+ interface DraftNotComplete {
362
+ status: 'incomplete' | 'invalid';
363
+ issues: DraftIssue[];
364
+ }
365
+ /**
366
+ * The result of `validateDraft`.
367
+ *
368
+ * There is deliberately no `success` boolean. A boolean has to call an
369
+ * incomplete draft either a success, which lets a half-written answer key
370
+ * through anything that checks it, or a failure, which is the conflation this
371
+ * result exists to remove. Branch on `status`.
372
+ */
373
+ type DraftValidationResult<T> = DraftComplete<T> | DraftNotComplete;
374
+ /** What `createDraft` hands an activity type's `authoring.createDraft`. */
375
+ interface DraftContext {
376
+ /**
377
+ * Returns a new id on every call. The SDK invents none: an id made here would
378
+ * need `crypto`, which plain-http origins do not have, or would be positional —
379
+ * and option ids reach the learner, so ids like `"a"` and `"b"` would tell them
380
+ * which option was written first.
381
+ */
382
+ newId: () => string;
383
+ }
384
+
385
+ /**
386
+ * Tells a draft that is not finished from a draft that is wrong.
387
+ *
388
+ * `validateActivity` answers "may this be stored?", and to that question an
389
+ * empty new question and a broken one are the same answer: no. An editor needs
390
+ * the difference — "Add a title" is a to-do, "Only one option can be marked
391
+ * correct" is an error — so a freshly added question does not read as a
392
+ * failure, disable a whole form, or blank a preview.
393
+ *
394
+ * - `complete` — the draft is a valid activity with nothing left to write.
395
+ * `data` is exactly what `validateActivity` returns for it.
396
+ * - `incomplete` — every problem is something not yet written.
397
+ * - `invalid` — at least one problem is wrong in a way writing more cannot fix.
398
+ *
399
+ * It is stricter than `validateActivity`, never looser: `complete` requires the
400
+ * activity's schema to pass, and a draft can fail here that the schema accepts
401
+ * (a title that is only whitespace). Content stored by an older build can
402
+ * therefore be valid and still not `complete`. `validateActivity` stays the
403
+ * check at your write boundary.
404
+ *
405
+ * Issues come from the type's `authoring.checkDraft`, plus every schema failure
406
+ * it did not already report at that path or inside it. (A failure at the root of
407
+ * the draft counts as reported only by an issue at the root, since every path is
408
+ * inside the root.) Such a failure is added as `invalid`: under the code
409
+ * `null_not_allowed` when the schema refused a `null` there, or an `undefined`
410
+ * entry in a list, and otherwise under the schema's own code and message. A type
411
+ * with no `checkDraft` therefore reports every schema failure as `invalid`.
412
+ *
413
+ * The codes documented in `docs/authoring.md` are a contract, and each is
414
+ * reported with its documented severity, whichever check reports it. A code
415
+ * passed through from the schema is not a contract: the code and its English
416
+ * message belong to the schema library, so give a code you do not recognise a
417
+ * generic message of your own.
418
+ *
419
+ * @throws UnknownActivityTypeError when `type` has no registered descriptor —
420
+ * including `'item-group'`, which is a container, not an activity type.
421
+ */
422
+ declare function validateDraft<T extends ActivityType>(type: T, draft: unknown): DraftValidationResult<ActivityDataMap[T]>;
423
+ /**
424
+ * A new, empty draft of an activity type, for an editor to start from.
425
+ *
426
+ * Every required field is present and nothing is authored, so the draft comes
427
+ * back from {@link validateDraft} as `incomplete`, and `validateActivity`
428
+ * rejects it until someone writes it. The SDK invents no ids: `newId` is called
429
+ * once per id the draft needs, and must return a different non-empty string each
430
+ * time.
431
+ *
432
+ * ```ts
433
+ * const draft = createDraft('multiple-choice', { newId: () => crypto.randomUUID() });
434
+ * ```
435
+ *
436
+ * @throws UnknownActivityTypeError when `type` has no registered descriptor.
437
+ * @throws Error when the type declares no `authoring.createDraft`, or when
438
+ * `newId` returns an empty or repeated id.
439
+ */
440
+ declare function createDraft<T extends ActivityType>(type: T, context: DraftContext): ActivityDataMap[T];
441
+
330
442
  /**
331
443
  * Content fingerprinting — "is this the same content the learner was served?"
332
444
  *
@@ -706,12 +818,48 @@ type ActivityTypeScoring<TData, TResponse> = {
706
818
  readonly reason: 'requires_async_grading';
707
819
  readonly partial?: (data: TData, response: TResponse | undefined) => DeferredScoringPartial;
708
820
  };
821
+ /**
822
+ * Authoring support for an activity type: the empty draft an editor starts
823
+ * from, and the rules that tell a draft that is unfinished from one that is
824
+ * wrong. Both are optional. Without `checkDraft`, `validateDraft` still works
825
+ * and reports every schema failure as `invalid`; without `createDraft`,
826
+ * `createDraft()` throws for the type.
827
+ */
828
+ interface ActivityTypeAuthoring<TData> {
829
+ /**
830
+ * A new draft: every required field present, nothing authored yet. It must
831
+ * come back from `validateDraft` as `incomplete` — never `invalid`, and never
832
+ * `complete` — so a freshly added question is not reported as an error, and
833
+ * cannot pass validation before anyone has written it.
834
+ */
835
+ readonly createDraft?: (context: DraftContext) => TData;
836
+ /**
837
+ * Reports what is missing (`incomplete`) and what is wrong (`invalid`) in a
838
+ * draft. Receives any plain object and must not throw on one.
839
+ *
840
+ * Report each problem at the path the schema reports it. `validateDraft` adds
841
+ * every schema failure as `invalid` unless an issue returned here sits at that
842
+ * path or inside it — so an issue at the wrong path leaves the schema's
843
+ * message standing beside yours, and makes an unfinished draft read as wrong.
844
+ * An issue inside a path accounts for every failure at that path, a list's
845
+ * length included, so report a rule about a whole list at the list's own path.
846
+ * A failure at the root of the draft is accounted for only by an issue at the
847
+ * root. A `null` the schema refuses, where nothing here reports it, comes back
848
+ * as `null_not_allowed` — except inside a plain `z.union`, which reports a
849
+ * failure once, at the union's own path, under its own code.
850
+ *
851
+ * A code documented in `docs/authoring.md` is reported with its documented
852
+ * severity. For a code of your own, a severity other than `'incomplete'` is
853
+ * reported as `invalid`.
854
+ */
855
+ readonly checkDraft?: (draft: Readonly<Record<string, unknown>>) => DraftIssue[];
856
+ }
709
857
  /**
710
858
  * A value-level description of an activity type: its contract (schema), its
711
859
  * grading, its redaction policy, and its interop facts. Registering a
712
- * descriptor makes `validateActivity`, `score`, `evaluate`, `redact`, and
713
- * `jsonSchemaFor` work for the type — an activity type is a value, not a
714
- * hardcoded union member (R1).
860
+ * descriptor makes `validateActivity`, `validateDraft`, `score`, `evaluate`,
861
+ * `redact`, and `jsonSchemaFor` work for the type — an activity type is a value,
862
+ * not a hardcoded union member (R1).
715
863
  */
716
864
  interface ActivityTypeDescriptor<TData extends {
717
865
  type: string;
@@ -736,6 +884,8 @@ interface ActivityTypeDescriptor<TData extends {
736
884
  readonly interop?: ActivityTypeInterop<TData>;
737
885
  /** The interaction-event kinds components for this type emit. */
738
886
  readonly interactions?: readonly string[];
887
+ /** Draft support for authoring tools. See {@link ActivityTypeAuthoring}. */
888
+ readonly authoring?: ActivityTypeAuthoring<TData>;
739
889
  }
740
890
  /**
741
891
  * Internal type-erased descriptor shape stored in the registry. Dispatch call
@@ -757,6 +907,7 @@ interface RegisteredActivityTypeDescriptor {
757
907
  readonly redactedSchema?: z.ZodType<unknown>;
758
908
  readonly interop?: ActivityTypeInterop<unknown>;
759
909
  readonly interactions?: readonly string[];
910
+ readonly authoring?: ActivityTypeAuthoring<unknown>;
760
911
  }
761
912
  /**
762
913
  * Identity helper that gives full type inference when authoring a descriptor:
@@ -997,4 +1148,4 @@ interface ThemeTokens {
997
1148
  '--lk-transition-base': string;
998
1149
  }
999
1150
 
1000
- export { ActivityData, ActivityMedia, ActivitySchemaError, type ActivityTypeDescriptor, type ActivityTypeInterop, type ActivityTypeScoring, type AttemptPlan, type AttemptPlanDrift, type AttemptPlanSlot, type AttemptProgress, type AttemptState, CriterionScore, DeferredScoringError, DeferredScoringPartial, type FieldPolicy, FillInTheBlanksData, FillInTheBlanksLearnerResponse, type FlattenSequenceOptions, type GradeFromRubricOptions, GradeRecord, ItemGroup, ItemOutcome, LearnerResponse, type MediaBudgetRef, type MediaPlayClaim, type MediaPlayGrant, type MediaPlayLedger, type MediaPlayLedgerEntry, type MissingOutcomePolicy, MultipleChoiceData, MultipleChoiceLearnerResponse, type PartialScoringResult, type PlanAttemptOptions, type PlannableEntry, type RedactOptions, type RedactedActivityData, type RedactedItemGroup, RedactedScoringError, type RegisteredActivityTypeDescriptor, type ResolvedPlaybackPolicy, type ResponseDiffEntry, ScoredItem, ScoringResult, type SeededShuffleOptions, type Sensitivity, SequenceEntry, SequenceSlot, type ShuffleVersion, type ThemeTokens, UnknownActivityTypeError, ValidationError, WrittenResponseData, WrittenResponseLearnerResponse, type XAPIInteractionType, assertRedacted, assertRedactedItemGroup, canonicalJson, contentHash, countWords, defineActivityType, diffResponses, entryKeyOf, fillInTheBlanksType, fingerprint, flattenSequence, getActivityTypeDescriptor, gradeFromRubric, hasGrade, hashSeed, isItemGroup, multipleChoiceType, outcomeFromGrade, planAttempt, planMediaBudgets, redact, redactItemGroup, registerActivityType, registeredActivityTypes, resolvePlaybackPolicy, restoreAttemptState, restoreMediaPlayLedger, scoredItemsFromPlan, seededShuffle, serializeAttemptState, serializeMediaPlayLedger, slotMediaKey, stimulusMediaKey, verifyAttemptPlan, writtenResponseType };
1151
+ export { ActivityData, ActivityDataMap, ActivityMedia, ActivitySchemaError, ActivityType, type ActivityTypeAuthoring, type ActivityTypeDescriptor, type ActivityTypeInterop, type ActivityTypeScoring, type AttemptPlan, type AttemptPlanDrift, type AttemptPlanSlot, type AttemptProgress, type AttemptState, CriterionScore, DeferredScoringError, DeferredScoringPartial, type DraftComplete, type DraftContext, type DraftIssue, type DraftNotComplete, type DraftSeverity, type DraftValidationResult, type FieldPolicy, FillInTheBlanksData, FillInTheBlanksLearnerResponse, type FlattenSequenceOptions, type GradeFromRubricOptions, GradeRecord, ItemGroup, ItemOutcome, LearnerResponse, type MediaBudgetRef, type MediaPlayClaim, type MediaPlayGrant, type MediaPlayLedger, type MediaPlayLedgerEntry, type MissingOutcomePolicy, MultipleChoiceData, MultipleChoiceLearnerResponse, type PartialScoringResult, type PlanAttemptOptions, type PlannableEntry, type RedactOptions, type RedactedActivityData, type RedactedItemGroup, RedactedScoringError, type RegisteredActivityTypeDescriptor, type ResolvedPlaybackPolicy, type ResponseDiffEntry, ScoredItem, ScoringResult, type SeededShuffleOptions, type Sensitivity, SequenceEntry, SequenceSlot, type ShuffleVersion, type ThemeTokens, UnknownActivityTypeError, ValidationError, WrittenResponseData, WrittenResponseLearnerResponse, type XAPIInteractionType, assertRedacted, assertRedactedItemGroup, canonicalJson, contentHash, countWords, createDraft, defineActivityType, diffResponses, entryKeyOf, fillInTheBlanksType, fingerprint, flattenSequence, getActivityTypeDescriptor, gradeFromRubric, hasGrade, hashSeed, isItemGroup, multipleChoiceType, outcomeFromGrade, planAttempt, planMediaBudgets, redact, redactItemGroup, registerActivityType, registeredActivityTypes, resolvePlaybackPolicy, restoreAttemptState, restoreMediaPlayLedger, scoredItemsFromPlan, seededShuffle, serializeAttemptState, serializeMediaPlayLedger, slotMediaKey, stimulusMediaKey, validateDraft, verifyAttemptPlan, writtenResponseType };
package/dist/index.js CHANGED
@@ -10,14 +10,14 @@ import {
10
10
  outcomeFromGrade,
11
11
  roundGrade,
12
12
  score
13
- } from "./chunk-LNA33IK7.js";
13
+ } from "./chunk-QX7P3CHP.js";
14
14
  import {
15
15
  XAPIVerb,
16
16
  XAPI_VERB_DISPLAY,
17
17
  validateXAPIStatement,
18
18
  xAPIBuilder,
19
19
  xapiDefinitionFor
20
- } from "./chunk-NHGXOOZ2.js";
20
+ } from "./chunk-ELTP5P6V.js";
21
21
  import {
22
22
  ItemGroupSchema,
23
23
  RedactedItemGroupSchema,
@@ -31,10 +31,11 @@ import {
31
31
  validateActivity,
32
32
  validateItemGroup,
33
33
  writtenResponseJsonSchema
34
- } from "./chunk-2M76F32Y.js";
34
+ } from "./chunk-6DM2H6BD.js";
35
35
  import {
36
36
  ActivitySchemaError,
37
37
  BlankConfigSchema,
38
+ DRAFT_ISSUE_SEVERITY,
38
39
  DeferredScoringError,
39
40
  FeedbackSchema,
40
41
  FillInTheBlanksDataSchema,
@@ -61,13 +62,16 @@ import {
61
62
  defineActivityType,
62
63
  fillInTheBlanksType,
63
64
  getActivityTypeDescriptor,
65
+ isRecord,
66
+ issue,
64
67
  levenshteinDistance,
65
68
  matchText,
66
69
  multipleChoiceType,
70
+ refusesEmpty,
67
71
  registerActivityType,
68
72
  registeredActivityTypes,
69
73
  writtenResponseType
70
- } from "./chunk-HCOWSGEZ.js";
74
+ } from "./chunk-AHEUYOT3.js";
71
75
 
72
76
  // src/content-hash.ts
73
77
  function canonicalJson(value, seen = /* @__PURE__ */ new Set()) {
@@ -571,6 +575,92 @@ function diffResponses(before, after) {
571
575
  return entries;
572
576
  }
573
577
 
578
+ // src/authoring/index.ts
579
+ function validateDraft(type, draft) {
580
+ const descriptor = getActivityTypeDescriptor(type);
581
+ if (descriptor === void 0) {
582
+ throw new UnknownActivityTypeError(String(type));
583
+ }
584
+ const reported = isRecord(draft) && descriptor.authoring?.checkDraft !== void 0 ? descriptor.authoring.checkDraft(draft).map(normalise) : [];
585
+ const issues = [...reported];
586
+ const parsed = descriptor.schema.safeParse(draft, { reportInput: true });
587
+ if (!parsed.success) {
588
+ const reportedEmpty = /* @__PURE__ */ new Set();
589
+ for (const schemaIssue of parsed.error.issues) {
590
+ const path = schemaIssue.path.map(String);
591
+ if (reported.some((known) => covers(known.path, path))) {
592
+ continue;
593
+ }
594
+ if (path.length > 0 && refusesEmpty(draft, schemaIssue)) {
595
+ const key = JSON.stringify(path);
596
+ if (!reportedEmpty.has(key)) {
597
+ reportedEmpty.add(key);
598
+ issues.push(
599
+ issue(
600
+ "null_not_allowed",
601
+ path,
602
+ typeof schemaIssue.path.at(-1) === "number" ? "This entry has no value. Remove it, or fill it in." : "This field cannot be null. Leave it out if it is optional, or give it a value."
603
+ )
604
+ );
605
+ }
606
+ continue;
607
+ }
608
+ issues.push({
609
+ path,
610
+ message: schemaIssue.message,
611
+ code: schemaIssue.code,
612
+ severity: "invalid"
613
+ });
614
+ }
615
+ }
616
+ if (parsed.success && issues.length === 0) {
617
+ return { status: "complete", data: parsed.data, issues };
618
+ }
619
+ const invalid = issues.some((found) => found.severity !== "incomplete");
620
+ return { status: invalid ? "invalid" : "incomplete", issues };
621
+ }
622
+ function createDraft(type, context) {
623
+ const descriptor = getActivityTypeDescriptor(type);
624
+ if (descriptor === void 0) {
625
+ throw new UnknownActivityTypeError(String(type));
626
+ }
627
+ const create = descriptor.authoring?.createDraft;
628
+ if (create === void 0) {
629
+ throw new Error(
630
+ `Activity type "${String(type)}" declares no authoring.createDraft, so there is no empty draft to create. Build the draft yourself; validateDraft works for every registered type.`
631
+ );
632
+ }
633
+ const issued = /* @__PURE__ */ new Set();
634
+ const newId = () => {
635
+ const id = context.newId();
636
+ if (typeof id !== "string" || id === "") {
637
+ throw new Error("createDraft: newId() must return a non-empty string.");
638
+ }
639
+ if (issued.has(id)) {
640
+ throw new Error(
641
+ `createDraft: newId() returned "${id}" twice. Ids within one draft must differ \u2014 two options sharing an id cannot be scored apart.`
642
+ );
643
+ }
644
+ issued.add(id);
645
+ return id;
646
+ };
647
+ return create({ newId });
648
+ }
649
+ function normalise(found) {
650
+ const documented = Object.hasOwn(DRAFT_ISSUE_SEVERITY, found.code) ? DRAFT_ISSUE_SEVERITY[found.code] : void 0;
651
+ return {
652
+ ...found,
653
+ path: found.path.map(String),
654
+ severity: documented ?? (found.severity === "incomplete" ? "incomplete" : "invalid")
655
+ };
656
+ }
657
+ function covers(reported, failed) {
658
+ if (failed.length === 0) {
659
+ return reported.length === 0;
660
+ }
661
+ return failed.every((segment, index) => reported[index] === segment);
662
+ }
663
+
574
664
  // src/redact.ts
575
665
  function mergePolicy(base, overrides) {
576
666
  if (overrides === void 0) {
@@ -644,10 +734,10 @@ function redact(data, options = {}) {
644
734
  if (!parsed.success) {
645
735
  throw new ActivitySchemaError(
646
736
  descriptor.type,
647
- parsed.error.issues.map((issue) => ({
648
- path: issue.path.map(String),
649
- message: issue.message,
650
- code: issue.code
737
+ parsed.error.issues.map((issue2) => ({
738
+ path: issue2.path.map(String),
739
+ message: issue2.message,
740
+ code: issue2.code
651
741
  }))
652
742
  );
653
743
  }
@@ -688,10 +778,10 @@ function assertRedacted(data) {
688
778
  if (!parsed.success) {
689
779
  throw new ActivitySchemaError(
690
780
  descriptor.type,
691
- parsed.error.issues.map((issue) => ({
692
- path: issue.path.map(String),
693
- message: issue.message,
694
- code: issue.code
781
+ parsed.error.issues.map((issue2) => ({
782
+ path: issue2.path.map(String),
783
+ message: issue2.message,
784
+ code: issue2.code
695
785
  }))
696
786
  );
697
787
  }
@@ -732,10 +822,10 @@ function redactItemGroup(group, options = {}) {
732
822
  if (!parsed.success) {
733
823
  throw new ActivitySchemaError(
734
824
  "item-group",
735
- parsed.error.issues.map((issue) => ({
736
- path: issue.path.map(String),
737
- message: issue.message,
738
- code: issue.code
825
+ parsed.error.issues.map((issue2) => ({
826
+ path: issue2.path.map(String),
827
+ message: issue2.message,
828
+ code: issue2.code
739
829
  }))
740
830
  );
741
831
  }
@@ -762,10 +852,10 @@ function assertRedactedItemGroup(data) {
762
852
  if (!parsed.success) {
763
853
  throw new ActivitySchemaError(
764
854
  "item-group",
765
- parsed.error.issues.map((issue) => ({
766
- path: issue.path.map(String),
767
- message: issue.message,
768
- code: issue.code
855
+ parsed.error.issues.map((issue2) => ({
856
+ path: issue2.path.map(String),
857
+ message: issue2.message,
858
+ code: issue2.code
769
859
  }))
770
860
  );
771
861
  }
@@ -776,9 +866,9 @@ function assertRedactedItemGroup(data) {
776
866
  if (error instanceof ActivitySchemaError) {
777
867
  throw new ActivitySchemaError(
778
868
  "item-group",
779
- error.errors.map((issue) => ({
780
- ...issue,
781
- path: ["items", String(index), ...issue.path]
869
+ error.errors.map((issue2) => ({
870
+ ...issue2,
871
+ path: ["items", String(index), ...issue2.path]
782
872
  }))
783
873
  );
784
874
  }
@@ -825,6 +915,7 @@ export {
825
915
  computePassThreshold,
826
916
  contentHash,
827
917
  countWords,
918
+ createDraft,
828
919
  defineActivityType,
829
920
  diffResponses,
830
921
  entryKeyOf,
@@ -865,6 +956,7 @@ export {
865
956
  stimulusJsonSchema,
866
957
  stimulusMediaKey,
867
958
  validateActivity,
959
+ validateDraft,
868
960
  validateItemGroup,
869
961
  validateXAPIStatement,
870
962
  verifyAttemptPlan,