Repository navigation
Releases
v0.3.0
Adds WebGLFileSystem.Persist() to the Unity package, so an outbox queue in a WebGL player survives a page reload, and widens the Unity tests to WebGL in CI, Android and the iOS Simulator. The NuGet packages have no code changes. The LRS still passes both ADL conformance batteries in full on SQLite and PostgreSQL (xAPI 2.0.0: 1435 of 1435, 1.0.3: 1365 of 1365) and the 5,299 tests pass on Linux. Of the 12 Unity tests, 11 pass in the editor and in a Linux IL2CPP player, where the reload test is skipped; in WebGL 8 pass and the 4 that need HttpClientHandler are skipped, before and after the reload.
Added
Xapi.Unity:WebGLFileSystem.Persist()savesApplication.persistentDataPathto IndexedDB in WebGL players, where Unity writes that directory only when asked, so aFileOutboxStorequeue there survives a page reload. Outside WebGL it does nothing. The WebGL tests check it across a reload.tests/Unity: CI also runs the tests in a WebGL player, in headless Chrome, and they pass in Android and iOS IL2CPP players on an arm64 Android emulator and the iOS Simulator. CONTRIBUTING.md has the steps for both. See docs/unity.md.
v0.2.1
A security update of the xapi-lrs image. The NuGet packages and the Unity package have no code changes and are released again so that every version matches the image. The LRS still passes both ADL conformance batteries in full on SQLite and PostgreSQL (xAPI 2.0.0: 1435 of 1435, 1.0.3: 1365 of 1365), the 5,299 tests pass on Linux, and the 11 Unity tests pass in the editor and in a Linux IL2CPP player.
Security
- The
xapi-lrsimage runs on the ASP.NET Core 10.0.14 runtime (was 10.0.13), the .NET security release of 25 October 2026 with fixes for four CVEs, and is built with SDK 10.0.403.
v0.2.0
The first release without a preview suffix. It adds Unity WebGL support through a new Unity package; the public API of the NuGet packages is unchanged. The LRS still passes both ADL conformance batteries in full on SQLite and PostgreSQL (xAPI 2.0.0: 1435 of 1435, 1.0.3: 1365 of 1365), the solution has about 5,300 tests, and the 11 Unity tests pass in the editor, in a Linux IL2CPP player and, locally, in WebGL.
Added
samples/LrsHostand thexapi-lrsimage:LrsHost:AllowedOrigins(LrsHost__AllowedOriginsin Docker) turns on CORS for the listed origins, so xAPI content in a browser on another origin can use the LRS. It is off by default.tests/Unity: Unity 6.3 tests ofXapi.Core,Xapi.ClientandXapi.Client.Outbox, installed with NuGetForUnity, that run in the editor, in IL2CPP players and in WebGL; CI runs them on Linux against the packages packed from the commit, andtests/Unity/WebGLruns a WebGL build in headless Chromium. See docs/unity.md.- A Unity package,
io.github.nurullahturkoglu.xapi.unityinsrc/Xapi.Unity, installed from git:UnityWebRequestHandler, anHttpMessageHandlerthat sends requests withUnityWebRequest, soXapiClientworks in WebGL players, whereHttpClient's default handler sends nothing.
Fixed
FileOutboxStore.OpenAsyncnever completed in Unity WebGL players when the store already had a journal, because the netstandard2.0 build read it with asynchronous file I/O, which needs the thread pool. That build now reads the journal synchronously; the net10.0 build is unchanged.
v0.1.0-preview.1
The first preview of all six packages. The LRS passes both ADL conformance batteries in full on SQLite and PostgreSQL (xAPI 2.0.0: 1435 of 1435, 1.0.3: 1365 of 1365), and the solution has about 5,300 tests.
Added
Xapi.Core: immutable models for xAPI 1.0.3 and 2.0.0 (IEEE 9274.1.1), strict and lenient JSON reading with all errors reported by JSON path, JSON writing in exact, ids and canonical formats, statement and batch validation, and statement equivalence as defined by the standard. Targets netstandard2.0 and net10.0.- The 1,922 statement cases generated by the ADL LRS conformance test suite templates (950 for 1.0.3, 972 for 2.0.0) run as unit tests against the parser.
- CI runs both ADL conformance batteries against lrsql to keep the conformance pipeline itself tested.
Xapi.Client: anHttpClient-based client for any xAPI 1.0.3 or 2.0.0 LRS. Covers statements (save, batch, get, voided, query withmorepaging, void), state, activity and agent profile documents with ETag preconditions, agents, activities and about. Sends and reads attachments as multipart/mixed, negotiates the version through/aboutwhen none is set, and maps 409 and 412 to typed exceptions.AddXapiClientregisters it withIHttpClientFactoryon net10.0. Targets netstandard2.0 and net10.0 and is Native AOT compatible.Xapi.Client.Outbox: a durable outbox for statements.StatementOutboxassigns ids before storing, so resends are idempotent, and delivers in order and in batches, with exponential backoff and jitter for transient errors,Retry-After, bisection of rejected batches, 409 resolution throughStatementEquivalence, a pause on 401 and 403, and inspectable, requeueable dead letters.FileOutboxStorekeeps an append-only, checksummed journal that survives a process killed mid-write;InMemoryOutboxStoreis for tests. Runs withFlushAsync, with a background loop that needs no host, or as a hosted service throughAddXapiClient(...).AddOutbox(...)on net10.0. Targets netstandard2.0 and net10.0. See docs/outbox.md.Xapi.Lrs: an LRS that runs inside an ASP.NET Core app (AddXapiLrs,MapXapiLrs). It negotiates xAPI 1.0.3 and 2.0, authenticates with Basic credentials (PBKDF2-hashed) or a delegated ASP.NET Core scheme, applies the xAPI scopes, and serves About and the Statement Resource write path: PUT and POST with strict validation, conflict detection by statement equivalence, voiding, one transaction per batch, and GET of single statements bystatementIdorvoidedStatementId. Statement queries support every filter of the standard (includingrelated_agents,related_activitiesand matches through StatementRefs, with voided statements left out), theexact,idsandcanonicalformats with Accept-Language, statelessmorepaging keyed on the stored order (MaxStatementsPerPage), Last-Modified and X-Experience-API-Consistent-Through. xAPI 1.0.x requests may use the Alternate Request Syntax. It also serves the State, Activity Profile and Agent Profile Resources: GET of one document or of the ids (withsince), PUT, POST with JSON merge, DELETE of one document or of all state (with or without a registration), HEAD, SHA-1 ETags andLast-Modified,If-Match/If-None-Matchchecked atomically with the write, and the 409 and 400 rules for missing preconditions that differ between 1.0.3 and 2.0. Documents are limited toMaxDocumentBytes(1 MiB by default). The Agents and Activities Resources return a Person and the canonical Activity definition. Targets net10.0.Xapi.Lrs: statement attachments. PUT and POST accept multipart/mixed, match parts to Attachment objects by SHA-2 (one part may serve several statements of a batch) and reject missing, extra or mismatching parts; data is stored once per SHA-2 and limited byMaxAttachmentBytes(50 MiB per request by default, 413 above it).attachments=truereturns the statements with their attachment data as multipart/mixed, for single statements and queries.Xapi.Lrs: signed statements are checked as IEEE 9274.1.1, 4.2.6 requires: an RS256, RS384 or RS512 JWS whose payload is equivalent to the statement (durations to 0.01 second), verified with thex5ccertificate when there is one. Malformed signatures get 400. See docs/signatures.md.Xapi.Client:StatementSignersigns a statement with an RSA key and an optional certificate chain, and returns the statement with its signature attachment and the JWS to send withSaveStatementAsync.Xapi.Lrs.SqliteandXapi.Lrs.PostgreSql: EF Core storage with migrations (UseSqlite,UsePostgreSql,MigrateOnStartup). The storage extension points (IStatementStore,IDocumentStore) are marked experimental (XAPI0001).samples/LrsHost: a standalone LRS on SQLite or PostgreSQL, used as the target of the conformance runs. CI runs both batteries against it on both databases.samples/LrsHost/Dockerfilebuilds it into a non-root image that stores SQLite data in/dataand will not start without credentials; CI builds and checks the image.- Samples
EmbedInAspNetCoreandFakeLrsInTests, and guides in docs/ (getting started, client, LRS, versions, migrating from TinCan.NET, conformance) whose C# blocks are compiled and checked bytests/Xapi.Documentation.Tests. benchmarks/Xapi.Benchmarks: BenchmarkDotNet benchmarks for statement parsing, writing, validation and equivalence, outbox enqueueing, and the LRS statement endpoints on SQLite and PostgreSQL, with measured results in docs/benchmarks.md.- A documentation site built with docfx from the guides and the XML docs, deployed to GitHub Pages from
main: https://nurullahturkoglu.github.io/xapi-dotnet/ Xapi.Lrs:MaxStatementsPerBatch(1,000 by default) limits the statements in one POST; larger batches get 413 before they are parsed.Xapi.Lrs:MigrateXapiLrsDatabaseAsync()on the app'sIServiceProviderapplies the database migrations as a separate deployment step, so migrating no longer needs the experimentalXapiLrsDbContext.Xapi.Core:XapiErrorCodes.AttachmentPayloadMissingandXapiErrorCodes.AttachmentPayloadUnreferenced, the codesXapi.ClientandXapi.Client.Outboxreport for attachment data. The values are unchanged.XapiError.Pathdocuments that the path ofattachment.payload.unreferencedis an index into the attachment data (attachments[0]), not a JSON path.Xapi.Lrs:AddXapiLrs(configuration, configure)binds the options from a configuration section, credentials included (Lrs:Credentials:0:Key,:Secret,:Scopes:0), and then runsconfigurefor what only code can set. Scopes are xAPI scope names; a misspelt key, an incomplete credential, an unknown scope or a value out of range stops the host at startup, and no message contains a secret.samples/LrsHostreads its options this way, so it accepts more than one credential. See Options from configuration.Xapi.Lrs: metrics on theXapi.Lrsmeter (xapi.lrs.request.duration,xapi.lrs.errors,xapi.lrs.statements.stored) and activities on theXapi.Lrsactivity source for storing and querying statements and writing documents, withSystem.Diagnosticsand no OpenTelemetry dependency. Tags never carry agents, IRIs, ids or credential keys. The names are constants ofXapiLrsDiagnostics. See Metrics and traces.Xapi.Lrs:AddXapiLrsHealthCheck()reports whether the database can be reached and is migrated.samples/LrsHostserves it at/health.Xapi.ClientandXapi.Client.Outbox: metrics on theXapi.Clientmeter: statements sent, retried and dead-lettered, and the statements pending in outboxes. The netstandard2.0 build ofXapi.Clientnow depends onSystem.Diagnostics.DiagnosticSource.- Releases publish the
samples/LrsHostimage to ghcr.io asghcr.io/nurullahturkoglu/xapi-lrs, for linux/amd64 and linux/arm64, tagged with the version and, for releases that are not previews,latest, with a signed build provenance attestation. See Running the sample LRS in Docker. - The packages have an icon, which the documentation site also uses as its logo and favicon.
Changed
Xapi.Core: reading a statement allocates about a quarter of what it did (5.2 KB instead of 19.7 KB for a 1 KB statement in strict mode) and takes about 40% less time. JSON paths are formatted only when an error is reported, and known property names are matched without creating strings or a hash set per object. Errors are unchanged. See docs/benchmarks.md.Xapi.Lrs: statement queries read each filter's own index (verb, registration, and one range per agent or activity usage, plus StatementRef matches) newest first and only as far as one page, instead of testing every statement. At 100,000 statements a verb with no matches takes 0.22 ms instead of 70 ms on SQLite and 0.84 ms instead of 52 ms on PostgreSQL, and anagentquery withrelated_agents2.3 ms instead of 581 ms and 5.9 ms instead of 15 ms.Xapi.Lrs: on SQLite, the matches through StatementRefs start from the filter's index when it matches fewer statements than there are StatementRefs, instead of looking up the target of every StatementRef. At 100,000 statements of which 10% are StatementRefs, a verb with no matches takes 0.42 ms instead of 23 ms and anagentquery withrelated_agents4.1 ms instead of 65 ms. PostgreSQL already chose this from its statistics. See docs/benchmarks.md.Xapi.Lrs:attachments=trueresponses are streamed, reading the data of one attachment at a time, and HEAD reads none.IStatementStore.GetAttachmentsAsyncis replaced byGetAttachmentSummariesAsyncandGetAttachmentContentAsync.Xapi.Lrs.Sqliteand `Xapi.Lrs.Postgre...