feat(async): AsyncTermWrapper and asynchronous mapping namespaces - #98
Draft
jeswr wants to merge 1 commit into
Draft
feat(async): AsyncTermWrapper and asynchronous mapping namespaces#98jeswr wants to merge 1 commit into
jeswr wants to merge 1 commit into
Conversation
Adds an asynchronous counterpart of the term-wrapping surface, for datasets that cannot be accessed synchronously (e.g. disk-backed or remote stores): - AsyncTermWrapper, bound to an AsyncDatasetCore from the @jeswr/async-dataset foundation package, with the same RDF/JS term surface as TermWrapper. Term-identity members stay synchronous; everything that touches the dataset returns promises. - Async mapping namespaces mirroring their synchronous siblings: AsyncRequiredFrom, AsyncRequiredAs, AsyncOptionalFrom, AsyncOptionalAs, AsyncSetFrom, AsyncLiteralAs and AsyncTermAs. - AsyncWrappingSet, the live asynchronously iterable set returned by AsyncSetFrom.subjectPredicate. - IAsyncTermAsValueMapping, IAsyncTermFromValueMapping and IAsyncTermWrapperConstructor types. Synchronous *From mappers plug into the async surface unchanged, since term creation is pure. - CardinalityError and MappingArgumentError (byte-identical to the copies proposed in rdfjs#89) thrown by the async mappings for arity violations and undefined mapper arguments. Tests mirror term_wrapper.test.ts over an n3 store exposed through a lazily resolved AsyncDatasetCore. All new mapping code is at 100% coverage except the defensive guards also uncovered in OptionalAs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 6, 2026
Draft
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
Adds an asynchronous counterpart of the term-wrapping surface, for datasets that cannot be accessed synchronously — for example datasets backed by disk or by remote storage. This is the substrate for interacting with remote datasets (#10) with the same mapping-class idiom the synchronous surface uses.
New public API, exported from
mod.tsunder an// Async surfacebanner:AsyncTermWrapper— mirrorsTermWrapper, but is bound to anAsyncDatasetCore(from the new@jeswr/async-datasetdependency, see below). Term-identity members (termType,value,equals, …) stay synchronous because they never touch the dataset; everything that reads or writes the dataset goes through the async mapping namespaces and returns promises. Because JavaScript property setters cannot return values, write mappings on async models are conventionallysetX(value)methods returningPromise<void>(documented on the class).AsyncRequiredFrom,AsyncRequiredAs,AsyncOptionalFrom,AsyncOptionalAs,AsyncSetFrom,AsyncLiteralAs(the seven primitive mappers:bigint,boolean,date,langString,number,string,symbol) andAsyncTermAs(instance,is,term). Notable async-specific behaviours:AsyncRequiredFrom.subjectPredicatepulls at most two quads from the (potentially lazy) async match view — the first iterator step must yield and the second must complete — instead of materializing the whole match.AsyncOptionalAs.objectmaterializes the existing matches before deleting any of them, so a lazy match view is never consumed while the dataset underneath it is being mutated.AsyncWrappingSet<T>— the live, asynchronously iterable set returned byAsyncSetFrom.subjectPredicate. Same member shape as the syncSetview withPromise-returning methods andfor awaititeration. (Exported because, unlike syncSetFromwhich can declareSet<T>, there is no built-in asynchronous set type to hide it behind.)IAsyncTermAsValueMapping,IAsyncTermFromValueMapping,IAsyncTermWrapperConstructor— async counterparts of the mapping types.IAsyncTermAsValueMappingmay return a value or a promise;IAsyncTermFromValueMappingis intentionally synchronous (term creation is pure), so the existingLiteralFrom/NamedNodeFrom/BlankNodeFrom/TermFrommappers plug into the async surface unchanged.The dataset dependency
The async dataset itself is not implemented here: this PR adds
@jeswr/async-dataset(github:jeswr/async-dataset#feat/async-dataset) as the library's first runtime dependency. It providesAsyncDatasetCore, a Promise/async-iterable implementation of the proposed RDF/JSAsyncDatasetCoreinterface (jeswr/types#2) over any synchronousDatasetCore— passed directly, as a promise, or as a lazy thunk that is only invoked on first use (which is what makes remote/disk-backed sources practical). Making that a visible, deliberate decision rather than baking a dataset implementation into this library keeps the wrapper focused on mapping.Errors
The async mappings throw typed errors from day one:
CardinalityError(required mapping found no value / more than one value; carriesterm,predicateand afound: "none" | "multiple"discriminator) andMappingArgumentError(anundefinedtermAs/termFrommapper). These two files are byte-identical to the copies proposed in #89 (which retrofits them onto the sync mappings), and themod.tsexport lines are the same two lines in the same place, so the two branches merge cleanly in either order; if #89 is rejected, the classes still stand on their own here.Relation to #61 / #73 / #74
Supersedes exactly the async/mapping slice of #73:
src/async/AsyncTermWrapper.ts,src/async/mapping/*(AsyncLiteralAs,AsyncOptionalAs,AsyncOptionalFrom,AsyncRequiredAs,AsyncRequiredFrom,AsyncSetFrom,AsyncTermAs),src/async/type/IAsync*.ts,src/async/AsyncWrappingSet.ts(minus its change-event members) and theasync_term_wrapper.test.ts+AsyncParent/AsyncChildtest models. It deliberately does not take on #73'sAsyncDatasetCore/AsyncNotifyingDatasetCore(replaced by the@jeswr/async-datasetdependency),AsyncDatasetWrapper, or the change-notification (on/off) surface — events are a separate concern (#93/#94 for the sync side). No overlap with #61 or #74.Tests
test/unit/async_term_wrapper.test.tsmirrorsterm_wrapper.test.tsacross the same Turtle fixture — value, term, object, arity, set and recursion mappings, plus the wrapper's RDF/JS term surface, directAsyncLiteralAs/AsyncTermAsguard tests,CardinalityErrorproperty assertions andMappingArgumentErrorcoverage of every async entry point. The n3 store is exposed through a lazily resolvedAsyncDatasetCore(thunk source), so the tests genuinely exercise asynchronous resolution. 176 tests, 0 failures; all new code is at 100% line coverage except the three defensive guards inAsyncOptionalAsthat are equally uncovered in syncOptionalAs.npx tsc,npm test,npx typedoc(same 6-warning baseline asmain) andnpm audit --omit=devall pass.