digitalbazaar / jsonld.js

A JSON-LD Processor and API implementation in JavaScript
https://json-ld.org/
Other
1.66k stars 195 forks source link

[feat] Synchronous expansion and compaction #418

Open RoboPhred opened 3 years ago

RoboPhred commented 3 years ago

I am interested in being able to take pre-resolved contexts and expand / compact json-ld synchronously.

My main use case is I have an api that is intended to emit a json-ld expanded form object representing itself. I want this call to be synchronous, and for the most part it is. However, I also need to take a user input of json-schema and include it in the expanded json-ld. There is a context for this, and I want to be able to synchronously process the user's json-schema with this context when generating the expanded json-ld.

I could work on this, but it might involve changing around the code base and splitting the algorithms apart so that they can be shared by both sync and async forms.

Is there any support for this by the maintainers of the library?

gkellogg commented 3 years ago

All implementations of JSON-LD allow you to provide a custom document loader, precicely to handle this situation. You can invoke the JSON-LD api with a documentLoader option providing a callback that will be used by the library to handle fetching remote documents, including contexts. In the spec this is described in 9.4.1. In jsonld.js, you can see how this is used here: https://github.com/digitalbazaar/jsonld.js/blob/master/lib/jsonld.js#L868.

Alternatively, you could provide a ResolvedContextCache instance which manages resolved contexts and preload those contexts that you want to avoid lookups for.

RoboPhred commented 3 years ago

Unfortunately this still leaves the API as asynchronous, as it is being wrapped through a promise. My primary concern is API design, where I am trying to keep my toJSONLD function synchronous and avoid converting it to return promises. While it would have been possible to synchronify the old callback-using design by always calling the loader callback synchronously, this is not possible for Promises as they defer their continuations until the next main thread loop.

The API I had in mind for json-ld would be a variant of expand and compact that would mandate a ResolvedContextCache and throw errors on encountering unresolved paths. This function would synchronously process the document and return the result immediately, not wrapped in a promise.

alexkreidler commented 3 years ago

+1 for this feature. My use-case is that I'm writing a frontend library that couples data definitions (JSON-LD frames) with UI components. In most cases, all the data gets fetched from a remote location at once, and then the library splits up the returned data so each component can render their piece of it.

Due to the JSON-LD.js API currently, I have to make all of those renders asynchronous, even if the data they depend on is already in fetched.

More specifically, my library roughly does the following

const ds = await jsonld.fromRDF(RDFdataset)
const framed = await jsonld.frame(ds, frame, opts)
return <Component data={framed}>

I don't see any reason in the spec that fromRDF shouldn't be able to be synchronous.

And the JSONLdProcessor frame() method only returns a Promise because it has several remote document fetching, expansion, and compaction steps. The actual Framing Algorithm has no async steps. If we could create sync APIs for expansion and compaction, we could do the same for framing.

In my situation of course, making these APIs synchronous would only be a benefit as long as they are performant enough to complete well before UI render. However, assuming that is true, I think synchronicity would be a boon for API simplicity, and for users where data is already entirely in the system, and just needs to be transformed transparently.