@conform-ed/contracts 0.0.3

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 (187) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-format.log +6 -0
  3. package/.turbo/turbo-lint.log +4 -0
  4. package/.turbo/turbo-test.log +196 -0
  5. package/.turbo/turbo-typecheck.log +2 -0
  6. package/README.md +52 -0
  7. package/caliper-v1_2-zod-templates.md +123 -0
  8. package/case-v1_1-zod-templates.md +290 -0
  9. package/cat-v1_0-zod-templates.md +73 -0
  10. package/cc-v1_3-zod-templates.md +531 -0
  11. package/cc-v1_4-zod-templates.md +247 -0
  12. package/clr-v2_0-zod-templates.md +117 -0
  13. package/cmi5-v1_0-zod-templates.md +9 -0
  14. package/lti-zod-templates.md +11 -0
  15. package/oneroster-v1_2-zod-templates.md +76 -0
  16. package/open-badges-v3_0-zod-templates.md +66 -0
  17. package/package.json +42 -0
  18. package/qti-v2_1-zod-templates.md +45 -0
  19. package/qti-v2_2-zod-templates.md +32 -0
  20. package/qti-v3_0_1-zod-templates.md +421 -0
  21. package/src/adapter.ts +83 -0
  22. package/src/caliper/v1_2/caliper_v1p2_bootcamp_schema.ts +781 -0
  23. package/src/caliper/v1_2/index.ts +37 -0
  24. package/src/caliper/v1_2/shared.ts +122 -0
  25. package/src/caliper/v1_2/textual_requirements.ts +219 -0
  26. package/src/case/v1_1/case_v1p1_cfassociation_jsonschema1.ts +1 -0
  27. package/src/case/v1_1/case_v1p1_cfassociationgrouping_jsonschema1.ts +1 -0
  28. package/src/case/v1_1/case_v1p1_cfassociationset_jsonschema1.ts +1 -0
  29. package/src/case/v1_1/case_v1p1_cfconceptset_jsonschema1.ts +1 -0
  30. package/src/case/v1_1/case_v1p1_cfdocument_jsonschema1.ts +1 -0
  31. package/src/case/v1_1/case_v1p1_cfdocumentset_jsonschema1.ts +1 -0
  32. package/src/case/v1_1/case_v1p1_cfitem_jsonschema1.ts +1 -0
  33. package/src/case/v1_1/case_v1p1_cfitemtypeset_jsonschema1.ts +1 -0
  34. package/src/case/v1_1/case_v1p1_cflicense_jsonschema1.ts +1 -0
  35. package/src/case/v1_1/case_v1p1_cfpackage_jsonschema1.ts +1 -0
  36. package/src/case/v1_1/case_v1p1_cfrubric_jsonschema1.ts +1 -0
  37. package/src/case/v1_1/case_v1p1_cfsubjectset_jsonschema1.ts +1 -0
  38. package/src/case/v1_1/case_v1p1_imsx_statusinfo_jsonschema1.ts +1 -0
  39. package/src/case/v1_1/case_v1p1_openapi3_restbinding_schema.ts +113 -0
  40. package/src/case/v1_1/index.ts +95 -0
  41. package/src/case/v1_1/shared.ts +384 -0
  42. package/src/cat/v1_0/cat_v1p0_restbinding_operations_schema.ts +75 -0
  43. package/src/cat/v1_0/index.ts +124 -0
  44. package/src/cat/v1_0/shared.ts +345 -0
  45. package/src/clr/v2_0/clr_v2p0_achievementcredential_schema.ts +16 -0
  46. package/src/clr/v2_0/clr_v2p0_clrcredential_schema.ts +9 -0
  47. package/src/clr/v2_0/clr_v2p0_endorsementcredential_schema.ts +11 -0
  48. package/src/clr/v2_0/clr_v2p0_getclrcredentialsresponse_schema.ts +1 -0
  49. package/src/clr/v2_0/clr_v2p0_imsx_statusinfo_schema.ts +8 -0
  50. package/src/clr/v2_0/clr_v2p0_profile_schema.ts +9 -0
  51. package/src/clr/v2_0/index.ts +22 -0
  52. package/src/clr/v2_0/shared.ts +206 -0
  53. package/src/cmi5/index.ts +3 -0
  54. package/src/cmi5/v1_0/index.ts +134 -0
  55. package/src/common-cartridge/v1_3/ccv1p3_imsccauth_v1p3.ts +26 -0
  56. package/src/common-cartridge/v1_3/ccv1p3_imscp_v1p2_v1p0.ts +352 -0
  57. package/src/common-cartridge/v1_3/ccv1p3_imscsmd_v1p0.ts +35 -0
  58. package/src/common-cartridge/v1_3/ccv1p3_imsdt_v1p3.ts +33 -0
  59. package/src/common-cartridge/v1_3/ccv1p3_imswl_v1p3.ts +23 -0
  60. package/src/common-cartridge/v1_3/ccv1p3_lomccltilink_v1p0.ts +14 -0
  61. package/src/common-cartridge/v1_3/ccv1p3_lommanifest_v1p0.ts +14 -0
  62. package/src/common-cartridge/v1_3/ccv1p3_lomresource_v1p0.ts +14 -0
  63. package/src/common-cartridge/v1_3/ccv1p3_qtiasiv1p2p1_v1p0.ts +964 -0
  64. package/src/common-cartridge/v1_3/index.ts +41 -0
  65. package/src/common-cartridge/v1_3/lom-internal.ts +396 -0
  66. package/src/common-cartridge/v1_3/shared.ts +68 -0
  67. package/src/common-cartridge/v1_4/core/ccv1p4_imscp_v1p2_v1p0.ts +360 -0
  68. package/src/common-cartridge/v1_4/core/ccv1p4_lommanifest_v1p0.ts +14 -0
  69. package/src/common-cartridge/v1_4/core/ccv1p4_lomresource_v1p0.ts +14 -0
  70. package/src/common-cartridge/v1_4/extension/cc_extresource_assignmentv1p0_v1p0.ts +61 -0
  71. package/src/common-cartridge/v1_4/extension/ccv1p4_cpextensionv1p2_v1p0.ts +20 -0
  72. package/src/common-cartridge/v1_4/extension/ims_openvideov1p0_v1p0.ts +325 -0
  73. package/src/common-cartridge/v1_4/index.ts +104 -0
  74. package/src/common-cartridge/v1_4/k12/ccv1p4_imscp_v1p2_v1p0.ts +19 -0
  75. package/src/common-cartridge/v1_4/k12/ccv1p4_imscp_v1p2_v1p0_thin.ts +17 -0
  76. package/src/common-cartridge/v1_4/k12/ccv1p4_lommanifest_v1p0.ts +14 -0
  77. package/src/common-cartridge/v1_4/k12/ccv1p4_lomresource_v1p0.ts +14 -0
  78. package/src/common-cartridge/v1_4/lom-internal.ts +476 -0
  79. package/src/common-cartridge/v1_4/shared/ccv1p4_imsccauth_v1p4.ts +26 -0
  80. package/src/common-cartridge/v1_4/shared/ccv1p4_imscsmd_v1p1.ts +36 -0
  81. package/src/common-cartridge/v1_4/shared/ccv1p4_imsdt_v1p4.ts +33 -0
  82. package/src/common-cartridge/v1_4/shared/ccv1p4_imslticc_v1p4.ts +45 -0
  83. package/src/common-cartridge/v1_4/shared/ccv1p4_imswl_v1p4.ts +23 -0
  84. package/src/common-cartridge/v1_4/shared/ccv1p4_lomccltilink_v1p0.ts +14 -0
  85. package/src/common-cartridge/v1_4/shared/ccv1p4_qtiasiv1p2p1_v1p0.ts +964 -0
  86. package/src/common-cartridge/v1_4/shared/imsbasiclti_v1p0p1.ts +24 -0
  87. package/src/common-cartridge/v1_4/shared/imslticm_v1p0.ts +23 -0
  88. package/src/common-cartridge/v1_4/shared/imslticp_v1p0.ts +57 -0
  89. package/src/common-cartridge/v1_4/shared/lineitem_v1p0.ts +35 -0
  90. package/src/common-cartridge/v1_4/shared/resourcea11ymetadata-20210915.ts +110 -0
  91. package/src/common-cartridge/v1_4/shared.ts +68 -0
  92. package/src/common-cartridge/v1_4/thin/ccv1p4_imscp_v1p2_v1p0.ts +243 -0
  93. package/src/common-cartridge/v1_4/thin/ccv1p4_lommanifest_v1p0.ts +14 -0
  94. package/src/common-cartridge/v1_4/thin/ccv1p4_lomresource_v1p0.ts +14 -0
  95. package/src/common-cartridge/v1_4/vdex/imsmd_loose_v1p3p2.ts +13 -0
  96. package/src/common-cartridge/v1_4/vdex/imsvdex_v1p0.ts +124 -0
  97. package/src/config.ts +121 -0
  98. package/src/index.ts +32 -0
  99. package/src/lti/ags/v2_0/index.ts +97 -0
  100. package/src/lti/deep-linking/v2_0/index.ts +100 -0
  101. package/src/lti/index.ts +26 -0
  102. package/src/lti/nrps/v2_0/index.ts +69 -0
  103. package/src/lti/proctoring/v1_0/index.ts +131 -0
  104. package/src/lti/shared.ts +66 -0
  105. package/src/lti/v1_3/index.ts +84 -0
  106. package/src/oneroster/v1_2/index.ts +156 -0
  107. package/src/oneroster/v1_2/or_v1p2_csv_binding_schema.ts +427 -0
  108. package/src/oneroster/v1_2/or_v1p2_gradebook_restbinding_schema.ts +120 -0
  109. package/src/oneroster/v1_2/or_v1p2_gradebook_service_schema.ts +243 -0
  110. package/src/oneroster/v1_2/or_v1p2_resource_restbinding_schema.ts +24 -0
  111. package/src/oneroster/v1_2/or_v1p2_resource_service_schema.ts +47 -0
  112. package/src/oneroster/v1_2/or_v1p2_rostering_restbinding_schema.ts +84 -0
  113. package/src/oneroster/v1_2/or_v1p2_rostering_service_schema.ts +288 -0
  114. package/src/oneroster/v1_2/shared.ts +90 -0
  115. package/src/open-badges/v3_0/index.ts +20 -0
  116. package/src/open-badges/v3_0/ob_v3p0_achievementcredential_schema.ts +17 -0
  117. package/src/open-badges/v3_0/ob_v3p0_endorsementcredential_schema.ts +11 -0
  118. package/src/open-badges/v3_0/ob_v3p0_getopenbadgecredentialsresponse_schema.ts +1 -0
  119. package/src/open-badges/v3_0/ob_v3p0_imsx_statusinfo_schema.ts +8 -0
  120. package/src/open-badges/v3_0/ob_v3p0_profile_schema.ts +9 -0
  121. package/src/open-badges/v3_0/shared.ts +458 -0
  122. package/src/qti/v2-internal.ts +1683 -0
  123. package/src/qti/v2_1/apipv1p0_qtiextv2p1_v1p0.ts +3 -0
  124. package/src/qti/v2_1/imsqti_metadata_v2p1.ts +3 -0
  125. package/src/qti/v2_1/imsqti_result_v2p1.ts +3 -0
  126. package/src/qti/v2_1/imsqti_usagedata_v2p1.ts +3 -0
  127. package/src/qti/v2_1/imsqti_v2p1p2.ts +71 -0
  128. package/src/qti/v2_1/index.ts +53 -0
  129. package/src/qti/v2_1/qtiv2p1_imscpv1p2_v1p0.ts +3 -0
  130. package/src/qti/v2_1/schemas.ts +15 -0
  131. package/src/qti/v2_1/shared.ts +3 -0
  132. package/src/qti/v2_2/apipv1p0_qtiextv2p2_v1p0p1.ts +3 -0
  133. package/src/qti/v2_2/imsqti_metadata_v2p2.ts +3 -0
  134. package/src/qti/v2_2/imsqti_result_v2p2.ts +3 -0
  135. package/src/qti/v2_2/imsqti_usagedata_v2p2.ts +3 -0
  136. package/src/qti/v2_2/imsqti_v2p2p4.ts +76 -0
  137. package/src/qti/v2_2/imsqtiv2p2p4_html5_v1p0.ts +8 -0
  138. package/src/qti/v2_2/index.ts +59 -0
  139. package/src/qti/v2_2/qtiv2p2_csm_v2p2.ts +4 -0
  140. package/src/qti/v2_2/qtiv2p2_imscpv1p2_v1p0.ts +3 -0
  141. package/src/qti/v2_2/schemas.ts +19 -0
  142. package/src/qti/v2_2/shared.ts +3 -0
  143. package/src/qti/v3_0_1/assessment-internal.ts +1509 -0
  144. package/src/qti/v3_0_1/imsqti_asiv3p0p1_v1p0.ts +57 -0
  145. package/src/qti/v3_0_1/imsqti_itemv3p0p1_v1p0.ts +60 -0
  146. package/src/qti/v3_0_1/imsqti_metadatav3p0_v1p0.ts +73 -0
  147. package/src/qti/v3_0_1/imsqti_outcomev3p0p1_v1p0.ts +11 -0
  148. package/src/qti/v3_0_1/imsqti_responseprocessingv3p0p1_v1p0.ts +17 -0
  149. package/src/qti/v3_0_1/imsqti_resultv3p0_v1p0.ts +222 -0
  150. package/src/qti/v3_0_1/imsqti_sectionv3p0p1_v1p0.ts +15 -0
  151. package/src/qti/v3_0_1/imsqti_stimulusv3p0p1_v1p0.ts +15 -0
  152. package/src/qti/v3_0_1/imsqti_testv3p0p1_v1p0.ts +49 -0
  153. package/src/qti/v3_0_1/imsqti_usagedatav3p0_v1p0.ts +77 -0
  154. package/src/qti/v3_0_1/imsqtiv3p0_afa3p0pnp_v1p0.ts +269 -0
  155. package/src/qti/v3_0_1/index.ts +50 -0
  156. package/src/qti/v3_0_1/processing-internal.ts +667 -0
  157. package/src/qti/v3_0_1/shared.ts +146 -0
  158. package/src/qti/v3_0_1/variables-internal.ts +274 -0
  159. package/src/shared/imsx-status.ts +49 -0
  160. package/src/summary.ts +70 -0
  161. package/src/vc-data-model/v2_0/index.ts +27 -0
  162. package/src/vc-data-model/v2_0/shared.ts +206 -0
  163. package/src/xapi/index.ts +12 -0
  164. package/src/xapi/shared.ts +444 -0
  165. package/src/xapi/v1_0_3/index.ts +169 -0
  166. package/src/xapi/v2_0/index.ts +256 -0
  167. package/test/caliper-v1_2.test.ts +270 -0
  168. package/test/case-v1_1.test.ts +147 -0
  169. package/test/cat-v1_0.test.ts +338 -0
  170. package/test/cc-v1_4.test.ts +239 -0
  171. package/test/clr-v2_0.test.ts +225 -0
  172. package/test/cmi5-v1_0.test.ts +196 -0
  173. package/test/contracts.test.ts +125 -0
  174. package/test/fixtures/cmi5/extended-cmi5.xml +72 -0
  175. package/test/fixtures/cmi5/simple-cmi5.xml +26 -0
  176. package/test/lti.test.ts +146 -0
  177. package/test/oneroster-v1_2.test.ts +234 -0
  178. package/test/open-badges-v3_0.test.ts +144 -0
  179. package/test/qti-v2_1.test.ts +146 -0
  180. package/test/qti-v2_2.test.ts +154 -0
  181. package/test/qti-v3_0_1.test.ts +576 -0
  182. package/test/schema-examples.test.ts +101 -0
  183. package/test/vc-data-model-v2_0.test.ts +63 -0
  184. package/test/xapi.test.ts +384 -0
  185. package/tsconfig.json +7 -0
  186. package/vc-data-model-v2_0-zod-templates.md +43 -0
  187. package/xapi-zod-templates.md +95 -0
@@ -0,0 +1,63 @@
1
+ import { expect, test } from "bun:test";
2
+
3
+ import { VcDataModelV2_0 } from "../src";
4
+
5
+ test("VerifiableCredentialSchema parses a minimal VC Data Model 2.0 credential", () => {
6
+ const parsed = VcDataModelV2_0.VerifiableCredentialSchema.safeParse({
7
+ "@context": ["https://www.w3.org/ns/credentials/v2"],
8
+ type: "VerifiableCredential",
9
+ issuer: "https://example.test/issuers/1",
10
+ validFrom: "2025-01-01T00:00:00Z",
11
+ credentialSubject: {
12
+ id: "did:example:subject-1",
13
+ },
14
+ });
15
+
16
+ expect(parsed.success).toBe(true);
17
+ });
18
+
19
+ test("VerifiablePresentationSchema parses a presentation carrying VC and JWS credentials", () => {
20
+ const parsed = VcDataModelV2_0.VerifiablePresentationSchema.safeParse({
21
+ "@context": ["https://www.w3.org/ns/credentials/v2"],
22
+ type: "VerifiablePresentation",
23
+ holder: "did:example:holder-1",
24
+ verifiableCredential: [
25
+ {
26
+ "@context": ["https://www.w3.org/ns/credentials/v2"],
27
+ type: "VerifiableCredential",
28
+ issuer: "https://example.test/issuers/1",
29
+ validFrom: "2025-01-01T00:00:00Z",
30
+ credentialSubject: {
31
+ id: "did:example:subject-1",
32
+ },
33
+ },
34
+ "header.payload.signature",
35
+ ],
36
+ });
37
+
38
+ expect(parsed.success).toBe(true);
39
+ });
40
+
41
+ test("VerifiableCredentialSchema rejects wrong core context", () => {
42
+ const parsed = VcDataModelV2_0.VerifiableCredentialSchema.safeParse({
43
+ "@context": ["https://example.test/context"],
44
+ type: "VerifiableCredential",
45
+ issuer: "https://example.test/issuers/1",
46
+ validFrom: "2025-01-01T00:00:00Z",
47
+ credentialSubject: {
48
+ id: "did:example:subject-1",
49
+ },
50
+ });
51
+
52
+ expect(parsed.success).toBe(false);
53
+ });
54
+
55
+ test("VcDataModel20DerivedZodTemplates exposes expected entry points", () => {
56
+ expect(VcDataModelV2_0.VcDataModel20DerivedZodTemplates.verifiableCredential).toBe(
57
+ VcDataModelV2_0.VerifiableCredentialSchema,
58
+ );
59
+ expect(VcDataModelV2_0.VcDataModel20DerivedZodTemplates.verifiablePresentation).toBe(
60
+ VcDataModelV2_0.VerifiablePresentationSchema,
61
+ );
62
+ expect(VcDataModelV2_0.VcDataModel20DerivedZodTemplates.proof).toBe(VcDataModelV2_0.ProofSchema);
63
+ });
@@ -0,0 +1,384 @@
1
+ import { expect, test } from "bun:test";
2
+ import { XapiV1_0_3, XapiV2_0 } from "@conform-ed/contracts";
3
+
4
+ const attachmentSha2 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
5
+
6
+ const validStatement = {
7
+ actor: {
8
+ objectType: "Agent",
9
+ mbox: "mailto:alice@example.com",
10
+ name: "Alice",
11
+ },
12
+ verb: {
13
+ id: "http://adlnet.gov/expapi/verbs/completed",
14
+ display: { "en-US": "completed" },
15
+ },
16
+ object: {
17
+ objectType: "Activity",
18
+ id: "http://example.com/activities/course-1",
19
+ definition: {
20
+ name: { "en-US": "Course 1" },
21
+ description: { "en-US": "Example course" },
22
+ interactionType: "choice",
23
+ correctResponsesPattern: ["a"],
24
+ choices: [{ id: "a", description: { "en-US": "Choice A" } }],
25
+ },
26
+ },
27
+ result: {
28
+ score: { scaled: 1, raw: 100, min: 0, max: 100 },
29
+ success: true,
30
+ completion: true,
31
+ duration: "PT1H",
32
+ },
33
+ context: {
34
+ registration: "550e8400-e29b-41d4-a716-446655440000",
35
+ platform: "conform-ed",
36
+ language: "en-US",
37
+ contextActivities: {
38
+ parent: [{ id: "http://example.com/activities/course-1" }],
39
+ },
40
+ },
41
+ timestamp: "2026-05-28T12:00:00.000Z",
42
+ version: "1.0.3",
43
+ } as const;
44
+
45
+ test("xAPI 1.0.3 statement schema accepts the core data model", () => {
46
+ expect(XapiV1_0_3.Schemas.Statement.safeParse(validStatement).success).toBe(true);
47
+ });
48
+
49
+ test("xAPI 2.0 statement schema accepts the same core data model", () => {
50
+ expect(
51
+ XapiV2_0.Schemas.Statement.safeParse({
52
+ ...validStatement,
53
+ version: "2.0",
54
+ }).success,
55
+ ).toBe(true);
56
+ });
57
+
58
+ test("xAPI 2.0 statement schema accepts IEEE 2.0 contextAgent/contextGroup objects", () => {
59
+ const statementV2 = {
60
+ ...validStatement,
61
+ version: "2.0",
62
+ context: {
63
+ ...validStatement.context,
64
+ contextAgents: [
65
+ {
66
+ objectType: "contextAgent",
67
+ agent: {
68
+ objectType: "Agent",
69
+ mbox: "mailto:observer@example.com",
70
+ },
71
+ relevantTypes: ["https://example.com/xapi/relevant-types/observer"],
72
+ },
73
+ ],
74
+ contextGroups: [
75
+ {
76
+ objectType: "contextGroup",
77
+ group: {
78
+ objectType: "Group",
79
+ account: {
80
+ homePage: "https://example.com/groups",
81
+ name: "cohort-7",
82
+ },
83
+ },
84
+ relevantTypes: ["https://example.com/xapi/relevant-types/cohort"],
85
+ },
86
+ ],
87
+ },
88
+ } as const;
89
+
90
+ expect(XapiV2_0.Schemas.Statement.safeParse(statementV2).success).toBe(true);
91
+ expect(XapiV2_0.Schemas.StatementSubmission.safeParse([statementV2]).success).toBe(true);
92
+ expect(
93
+ XapiV2_0.Schemas.StatementResult.safeParse({
94
+ statements: [statementV2],
95
+ more: "/xapi/statements?more=page-2",
96
+ }).success,
97
+ ).toBe(true);
98
+ expect(
99
+ XapiV2_0.Schemas.MultipartRequest.safeParse({
100
+ contentType: "multipart/mixed",
101
+ parts: [
102
+ {
103
+ contentType: "application/json",
104
+ body: statementV2,
105
+ },
106
+ [
107
+ {
108
+ headers: {
109
+ "Content-Type": "application/json",
110
+ "Content-Transfer-Encoding": "binary",
111
+ "X-Experience-API-Hash": attachmentSha2,
112
+ },
113
+ body: new Uint8Array([0x7b, 0x7d]),
114
+ },
115
+ ],
116
+ ],
117
+ }).success,
118
+ ).toBe(true);
119
+
120
+ expect(XapiV1_0_3.Schemas.Statement.safeParse(statementV2).success).toBe(false);
121
+ });
122
+
123
+ test("xAPI statement submission schema accepts batches", () => {
124
+ expect(XapiV1_0_3.Schemas.StatementSubmission.safeParse([validStatement]).success).toBe(true);
125
+ });
126
+
127
+ test("xAPI document, listing, and about schemas validate LRS metadata", () => {
128
+ expect(
129
+ XapiV1_0_3.Schemas.AboutResource.safeParse({
130
+ version: ["1.0.3"],
131
+ extensions: {
132
+ "https://example.com/xapi/lrs/extensions/features": ["person", "statement-result"],
133
+ },
134
+ }).success,
135
+ ).toBe(true);
136
+
137
+ expect(
138
+ XapiV1_0_3.Schemas.StateDocumentQuery.safeParse({
139
+ activityId: "http://example.com/activities/course-1",
140
+ agent: { mbox: "mailto:alice@example.com" },
141
+ registration: "550e8400-e29b-41d4-a716-446655440000",
142
+ stateId: "progress",
143
+ }).success,
144
+ ).toBe(true);
145
+
146
+ expect(
147
+ XapiV1_0_3.Schemas.XapiDocument.safeParse({
148
+ contentType: "application/json",
149
+ body: { progress: 0.5 },
150
+ etag: '"abc123"',
151
+ lastModified: "2026-05-28T12:00:00.000Z",
152
+ }).success,
153
+ ).toBe(true);
154
+
155
+ expect(
156
+ XapiV1_0_3.Schemas.StateDocumentListingQuery.safeParse({
157
+ activityId: "http://example.com/activities/course-1",
158
+ agent: { mbox: "mailto:alice@example.com" },
159
+ since: "2026-05-27T12:00:00.000Z",
160
+ }).success,
161
+ ).toBe(true);
162
+
163
+ expect(
164
+ XapiV1_0_3.Schemas.AgentProfileDocumentListingQuery.safeParse({
165
+ agent: { mbox: "mailto:alice@example.com" },
166
+ since: "2026-05-27T12:00:00.000Z",
167
+ }).success,
168
+ ).toBe(true);
169
+
170
+ expect(
171
+ XapiV1_0_3.Schemas.ActivityProfileDocumentListingQuery.safeParse({
172
+ activityId: "http://example.com/activities/course-1",
173
+ since: "2026-05-27T12:00:00.000Z",
174
+ }).success,
175
+ ).toBe(true);
176
+
177
+ expect(XapiV1_0_3.Schemas.XapiDocumentIdList.safeParse(["bookmark", "progress"]).success).toBe(true);
178
+ });
179
+
180
+ test("xAPI validation rejects statements missing required fields", () => {
181
+ expect(
182
+ XapiV1_0_3.Schemas.Statement.safeParse({
183
+ verb: { id: "http://adlnet.gov/expapi/verbs/completed" },
184
+ object: { id: "http://example.com/activities/course-1" },
185
+ }).success,
186
+ ).toBe(false);
187
+ });
188
+
189
+ test("xAPI statement result, person object, and resource queries validate missing spec objects", () => {
190
+ expect(
191
+ XapiV1_0_3.Schemas.StatementResult.safeParse({
192
+ statements: [validStatement],
193
+ more: "/xapi/statements?more=opaque-token",
194
+ }).success,
195
+ ).toBe(true);
196
+
197
+ expect(
198
+ XapiV1_0_3.Schemas.StatementResult.safeParse({
199
+ statements: [validStatement],
200
+ more: "https://example.com/xapi/statements?more=absolute",
201
+ }).success,
202
+ ).toBe(false);
203
+
204
+ expect(
205
+ XapiV1_0_3.Schemas.Person.safeParse({
206
+ objectType: "Person",
207
+ name: ["Alice Example"],
208
+ mbox: ["mailto:alice@example.com"],
209
+ account: [
210
+ {
211
+ homePage: "https://example.com/accounts",
212
+ name: "alice",
213
+ },
214
+ ],
215
+ }).success,
216
+ ).toBe(true);
217
+
218
+ expect(
219
+ XapiV1_0_3.Schemas.AgentsResourceQuery.safeParse({
220
+ agent: { mbox: "mailto:alice@example.com" },
221
+ }).success,
222
+ ).toBe(true);
223
+
224
+ expect(
225
+ XapiV1_0_3.Schemas.ActivitiesResourceQuery.safeParse({
226
+ activityId: "http://example.com/activities/course-1",
227
+ }).success,
228
+ ).toBe(true);
229
+
230
+ expect(
231
+ XapiV1_0_3.Schemas.StatementsQuery.safeParse({
232
+ agent: { mbox: "mailto:alice@example.com" },
233
+ verb: "http://adlnet.gov/expapi/verbs/completed",
234
+ since: "2026-05-27T12:00:00.000Z",
235
+ format: "canonical",
236
+ attachments: true,
237
+ ascending: false,
238
+ }).success,
239
+ ).toBe(true);
240
+ });
241
+
242
+ // LRS/Content schema tests
243
+ test("xAPI HTTP method enum validates LRS operations", () => {
244
+ expect(XapiV1_0_3.Schemas.HttpMethod.safeParse("GET").success).toBe(true);
245
+ expect(XapiV1_0_3.Schemas.HttpMethod.safeParse("POST").success).toBe(true);
246
+ expect(XapiV1_0_3.Schemas.HttpMethod.safeParse("PUT").success).toBe(true);
247
+ expect(XapiV1_0_3.Schemas.HttpMethod.safeParse("DELETE").success).toBe(true);
248
+ expect(XapiV1_0_3.Schemas.HttpMethod.safeParse("PATCH").success).toBe(false);
249
+ });
250
+
251
+ test("xAPI error response schema validates LRS error payloads", () => {
252
+ expect(
253
+ XapiV1_0_3.Schemas.ErrorResponse.safeParse({
254
+ code: "400",
255
+ message: "Invalid request",
256
+ details: "Missing required field: actor",
257
+ }).success,
258
+ ).toBe(true);
259
+
260
+ expect(
261
+ XapiV1_0_3.Schemas.ErrorResponse.safeParse({
262
+ code: "500",
263
+ message: "Internal server error",
264
+ }).success,
265
+ ).toBe(true);
266
+
267
+ expect(
268
+ XapiV1_0_3.Schemas.ErrorResponse.safeParse({
269
+ code: "400",
270
+ message: "Invalid request",
271
+ extra: "should not be here",
272
+ }).success,
273
+ ).toBe(false);
274
+ });
275
+
276
+ test("xAPI error codes validate standard HTTP status responses", () => {
277
+ expect(XapiV1_0_3.Schemas.ErrorCode.safeParse("400").success).toBe(true);
278
+ expect(XapiV1_0_3.Schemas.ErrorCode.safeParse("401").success).toBe(true);
279
+ expect(XapiV1_0_3.Schemas.ErrorCode.safeParse("403").success).toBe(true);
280
+ expect(XapiV1_0_3.Schemas.ErrorCode.safeParse("404").success).toBe(true);
281
+ expect(XapiV1_0_3.Schemas.ErrorCode.safeParse("412").success).toBe(true);
282
+ expect(XapiV1_0_3.Schemas.ErrorCode.safeParse("500").success).toBe(true);
283
+ expect(XapiV1_0_3.Schemas.ErrorCode.safeParse("999").success).toBe(false);
284
+ });
285
+
286
+ test("xAPI concurrency schema validates ETags and conditional headers", () => {
287
+ expect(
288
+ XapiV1_0_3.Schemas.Concurrency.safeParse({
289
+ etag: '"abc123"',
290
+ }).success,
291
+ ).toBe(true);
292
+
293
+ expect(
294
+ XapiV1_0_3.Schemas.Concurrency.safeParse({
295
+ ifMatch: '"abc123"',
296
+ }).success,
297
+ ).toBe(true);
298
+
299
+ expect(
300
+ XapiV1_0_3.Schemas.Concurrency.safeParse({
301
+ ifNoneMatch: '"abc123"',
302
+ }).success,
303
+ ).toBe(true);
304
+
305
+ expect(XapiV1_0_3.Schemas.Concurrency.safeParse({}).success).toBe(true);
306
+ });
307
+
308
+ test("xAPI multipart attachment part schema validates attachment data structure", () => {
309
+ expect(
310
+ XapiV1_0_3.Schemas.MultipartAttachmentPart.safeParse({
311
+ headers: {
312
+ "Content-Type": "image/png",
313
+ "Content-Transfer-Encoding": "binary",
314
+ "X-Experience-API-Hash": attachmentSha2,
315
+ },
316
+ body: new Uint8Array([0x89, 0x50, 0x4e, 0x47]),
317
+ }).success,
318
+ ).toBe(true);
319
+
320
+ expect(
321
+ XapiV1_0_3.Schemas.MultipartAttachmentPart.safeParse({
322
+ headers: {
323
+ "Content-Type": "image/png",
324
+ "Content-Transfer-Encoding": "base64",
325
+ "X-Experience-API-Hash": attachmentSha2,
326
+ },
327
+ body: "iVBORw0KGgoAAAANSUhEUgAAAAUA",
328
+ }).success,
329
+ ).toBe(false);
330
+ });
331
+
332
+ test("xAPI multipart request schema validates statement with attachments", () => {
333
+ expect(
334
+ XapiV1_0_3.Schemas.MultipartRequest.safeParse({
335
+ contentType: "multipart/mixed",
336
+ parts: [
337
+ {
338
+ contentType: "application/json",
339
+ body: validStatement,
340
+ },
341
+ [
342
+ {
343
+ headers: {
344
+ "Content-Type": "image/png",
345
+ "Content-Transfer-Encoding": "binary",
346
+ "X-Experience-API-Hash": attachmentSha2,
347
+ },
348
+ body: new Uint8Array([0x89, 0x50, 0x4e, 0x47]),
349
+ },
350
+ ],
351
+ ],
352
+ }).success,
353
+ ).toBe(true);
354
+ });
355
+
356
+ test("xAPI resource schema models LRS endpoint definitions", () => {
357
+ expect(
358
+ XapiV1_0_3.Schemas.Resource.safeParse({
359
+ name: "Statements",
360
+ description: "Store and retrieve xAPI statements",
361
+ methods: ["GET", "POST", "PUT"],
362
+ }).success,
363
+ ).toBe(true);
364
+
365
+ expect(
366
+ XapiV1_0_3.Schemas.Resource.safeParse({
367
+ name: "Statements",
368
+ description: "Store and retrieve xAPI statements",
369
+ methods: [],
370
+ }).success,
371
+ ).toBe(false);
372
+ });
373
+
374
+ test("xAPI request and response headers are properly enumerated", () => {
375
+ expect(XapiV1_0_3.Schemas.RequestHeader.safeParse("Authorization").success).toBe(true);
376
+ expect(XapiV1_0_3.Schemas.RequestHeader.safeParse("If-Match").success).toBe(true);
377
+ expect(XapiV1_0_3.Schemas.RequestHeader.safeParse("X-Experience-API-Hash").success).toBe(true);
378
+ expect(XapiV1_0_3.Schemas.RequestHeader.safeParse("X-Experience-API-Version").success).toBe(true);
379
+ expect(XapiV1_0_3.Schemas.RequestHeader.safeParse("Custom-Header").success).toBe(false);
380
+
381
+ expect(XapiV1_0_3.Schemas.ResponseHeader.safeParse("ETag").success).toBe(true);
382
+ expect(XapiV1_0_3.Schemas.ResponseHeader.safeParse("X-Experience-API-Consistent-Through").success).toBe(true);
383
+ expect(XapiV1_0_3.Schemas.ResponseHeader.safeParse("Authorization").success).toBe(false);
384
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "types": ["bun"]
5
+ },
6
+ "include": ["src", "test"]
7
+ }
@@ -0,0 +1,43 @@
1
+ # Verifiable Credentials Data Model 2.0 -> Zod template notes
2
+
3
+ ## Published specification
4
+
5
+ - W3C Recommendation: `https://www.w3.org/TR/vc-data-model-2.0/`
6
+
7
+ This note accompanies `packages/contracts/src/vc-data-model/v2_0/`.
8
+
9
+ ---
10
+
11
+ ## 1. Scope choice
12
+
13
+ VC Data Model 2.0 does not ship as a single 1EdTech-style JSON schema bundle that maps one-to-one to this repository's versioned schema-port workflow.
14
+
15
+ To integrate it cleanly for Open Badges 3.0 and CLR 2.0, this package implements the shared VC and credential primitives used by both standards:
16
+
17
+ - `VerifiableCredentialSchema`
18
+ - `VerifiablePresentationSchema`
19
+ - `CredentialSubjectSchema`
20
+ - `CredentialSchemaSchema`
21
+ - `CredentialStatusSchema`
22
+ - `RefreshServiceSchema`
23
+ - `TermsOfUseSchema`
24
+ - `ProofSchema`
25
+ - `EvidenceSchema`
26
+ - `HolderSchema`
27
+
28
+ Grouped as `VcDataModel20DerivedZodTemplates`.
29
+
30
+ ---
31
+
32
+ ## 2. Relationship to Open Badges and CLR
33
+
34
+ This VC Data Model surface is intentionally shared infrastructure. Open Badges 3.0 and CLR 2.0 version bundles compose on top of it, with each standard adding its own context/type constraints and domain objects.
35
+
36
+ This avoids duplicated logic and keeps cross-standard behavior aligned.
37
+
38
+ ---
39
+
40
+ ## 3. Public surface and naming
41
+
42
+ - package-root namespace: `VcDataModelV2_0`
43
+ - package subpath: `@conform-ed/contracts/vc-data-model/v2_0`
@@ -0,0 +1,95 @@
1
+ # xAPI Zod templates
2
+
3
+ ## Scope
4
+
5
+ This bundle covers:
6
+
7
+ - xAPI 1.0.3 statement/data models
8
+ - IEEE xAPI 2.0 statement/data models
9
+ - Statement result containers and Person objects
10
+ - LRS document resources (State, AgentProfile, ActivityProfile)
11
+ - Statement, Agents, and Activities resource query shapes
12
+ - Document id list responses for State/Profile resources
13
+ - LRS resource and endpoint schemas
14
+ - HTTP transport and concurrency control (ETags, conditional requests)
15
+ - Multipart attachment transmission with binary hashing
16
+ - Error handling and HTTP status codes
17
+ - About/version metadata
18
+
19
+ ## Source references
20
+
21
+ These documents were used as normative references while modeling the xAPI payload contracts:
22
+
23
+ - ADL xAPI 1.0.3 spec documents:
24
+ - `xAPI-About.md`
25
+ - `xAPI-Data.md`
26
+ - `xAPI-Communication.md` (including LRS and statement submission guidance)
27
+ - IEEE xAPI 2.0 base-standard documents:
28
+ - `9274.1.1 xAPI Base Standard Front Matter.md`
29
+ - `9274.1.1 xAPI Base Standard Overview.md`
30
+ - `9274.1.1 xAPI Base Standard for Content.md`
31
+ - `9274.1.1 xAPI Base Standard for LRSs.md`
32
+
33
+ ## Entry points
34
+
35
+ - `@conform-ed/contracts/xapi`
36
+ - `@conform-ed/contracts/xapi/v1_0_3`
37
+ - `@conform-ed/contracts/xapi/v2_0`
38
+
39
+ ## Schemas
40
+
41
+ ### Core Data Models
42
+
43
+ - `Agent` / `Group` — Actor types with inverse-functional identifier validation
44
+ - `Person` — Aggregated Agents Resource response with array-valued identifiers
45
+ - `Verb` — Action vocabulary with display language support
46
+ - `Activity` — Learning object with optional activity definition
47
+ - `Result` — Statement outcome with score, success, completion, and duration
48
+ - `Context` — Contextual information including registration, language, and activities
49
+ - `ContextAgent` / `ContextGroup` (IEEE 2.0) — Statement-scoped contextual agents/groups with optional `relevantTypes`
50
+ - `Attachment` — Binary attachment metadata with SHA2 hashing
51
+ - `Statement` — Complete xAPI statement combining actor, verb, object, result, and context
52
+ - `StatementResult` — `/statements` query response envelope containing `statements` plus optional `more`
53
+
54
+ ### LRS Document Resources
55
+
56
+ - `StatementsQuery` — `/statements` query parameters (statement ids, agent/activity filters, date windows, format, attachments)
57
+ - `AgentsResourceQuery` — `/agents` request payload/query wrapper for a target Agent
58
+ - `ActivitiesResourceQuery` — `/activities` request payload/query wrapper for a target Activity id
59
+ - `StateDocumentQuery` — Request parameters for state document access (activityId, agent, stateId, registration)
60
+ - `StateDocumentListingQuery` — Request parameters for listing state ids, including optional `since`
61
+ - `AgentProfileDocumentQuery` — Request parameters for agent profile access
62
+ - `AgentProfileDocumentListingQuery` — Request parameters for listing agent profile ids
63
+ - `ActivityProfileDocumentQuery` — Request parameters for activity profile access
64
+ - `ActivityProfileDocumentListingQuery` — Request parameters for listing activity profile ids
65
+ - `XapiDocument` — Response envelope for document retrieval (contentType, body, etag, lastModified)
66
+ - `XapiDocumentIdList` — Top-level JSON array response for State/Profile id listings
67
+
68
+ ### Transport and Concurrency
69
+
70
+ - `HttpMethod` — Enumeration of LRS HTTP methods (GET, HEAD, PUT, POST, DELETE)
71
+ - `RequestHeader` — HTTP request headers supported by xAPI (Authorization, If-Match, etc.)
72
+ - `ResponseHeader` — HTTP response headers (ETag, X-Experience-API-Version, etc.)
73
+ - `Concurrency` — Concurrency control with ETag and conditional request headers (If-Match, If-None-Match)
74
+ - `ErrorCode` — Standard HTTP error codes returned by LRS (400, 401, 403, 404, 409, 411, 412, 413, 500, 501)
75
+ - `ErrorResponse` — Error payload with code, message, and optional details
76
+
77
+ ### Multipart Attachments
78
+
79
+ - `MultipartAttachmentPart` — Binary attachment data with required Content-Type, Content-Transfer-Encoding (binary), and X-Experience-API-Hash headers
80
+ - `MultipartRequest` — Full multipart/mixed request envelope with JSON statement(s) followed by binary attachment parts
81
+
82
+ ### Service Metadata
83
+
84
+ - `AboutResource` — LRS about/version endpoint response listing supported xAPI versions
85
+ - `Resource` — Endpoint definition with name, description, and supported HTTP methods
86
+
87
+ ## Notes
88
+
89
+ - Structured statement objects and all domain types are strict (`strictObject()`), catching misspelled or extra properties.
90
+ - Document bodies are intentionally permissive (`z.unknown()`) because xAPI allows arbitrary document content types.
91
+ - Agent and Group schemas enforce at-least-one-identifier validation via `.refine()`, matching the spec intent that agents must be identifiable.
92
+ - Person objects are modeled separately from Agents because the `/agents` resource returns array-valued identifier fields rather than single IFIs.
93
+ - Concurrency control and multipart transmission are modeled as separate concerns for flexibility in HTTP client implementations.
94
+ - Version 2.0 reuses the 1.0.3 model where structurally equivalent, but overrides Context/Statement-family schemas to add IEEE `contextAgent`, `contextGroup`, and `relevantTypes`.
95
+ - Strict error codes (400, 401, 403, 404, 409, 411, 412, 413, 500, 501) align with xAPI specification guidance for LRS error responses.