Skip to content
10 changes: 5 additions & 5 deletions .github/workflows/main.yml → .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
node-version: [18.x]
node-version: [24.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
Expand All @@ -23,7 +23,7 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
node-version: [14.x, 16.x, 18.x, 20.x, 22.x]
node-version: [18.x, 20.x, 22.x, 24.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
Expand All @@ -41,7 +41,7 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
node-version: [16.x]
node-version: [24.x]
bundler: [webpack, browserify]
steps:
- uses: actions/checkout@v4
Expand All @@ -62,7 +62,7 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
node-version: [16.x]
node-version: [24.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
Expand All @@ -78,7 +78,7 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
node-version: [18.x]
node-version: [24.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
Expand Down
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
# jsonld ChangeLog

## 9.0.0 - 2025-xx-xx

### Changed
- **BREAKING**: Drop support for Node.js < 18.
- **BREAKING**: Upgrade dependencies.
- `@digitalbazaar/http-client@4`.
- `canonicalize@2`.
- `rdf-canonize@4`: See the [rdf-canonize][] 4.0.0 changelog for
**important** changes and upgrade notes. Of note:
- The `URDNA2015` default algorithm has been changed to `RDFC-1.0` from
[rdf-canon][].
- Complexity control defaults `maxWorkFactor` or `maxDeepIterations` may
need to be adjusted to process graphs with certain blank node constructs.
- A `signal` option is available to use an `AbortSignal` to limit resource
usage.
- The internal digest algorithm can be changed.
- Update development dependencies.
- Update karma testing.
- Remove older fixes in favor of more default behavior.
- Update bundle build.
- Use newer corejs version.
- Build with modern browserslist defaults and no IE support.
- Support for older browsers requires a custom build.
- Refactor test framework.
- Test runtime loads test files from a web server.
- Allows testing of manifests on remote web servers.
- Trading off some performance to align node and browser testing.
- Moves some test setup code into config data and manifest.

### Removed
- **BREAKING**: Remove `application/nquads` alias for `application/n-quads`.

## 8.3.3 - 2024-12-21

### Added
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
jsonld.js
=========

[![Build status](https://img.shields.io/github/actions/workflow/status/digitalbazaar/jsonld.js/main.yml)](https://github.com/digitalbazaar/jsonld.js/actions/workflows/main.yml)
[![Build status](https://img.shields.io/github/actions/workflow/status/digitalbazaar/jsonld.js/main.yaml)](https://github.com/digitalbazaar/jsonld.js/actions/workflows/main.yaml)
[![Coverage status](https://img.shields.io/codecov/c/github/digitalbazaar/jsonld.js)](https://codecov.io/gh/digitalbazaar/jsonld.js)
[![npm](https://img.shields.io/npm/v/jsonld)](https://npm.im/jsonld)

Expand Down
104 changes: 60 additions & 44 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,40 @@
*/
const os = require('os');
const webpack = require('webpack');
const {TestServer} = require('./tests/test-server.js');

// karma test server proxy details
const _proxyTestsPrefix = '/tests';

let testServer;

// shutdown test server "reporter" hook
function ShutdownTestServer(baseReporterDecorator) {
baseReporterDecorator(this);

this.onRunComplete = async function() {
await testServer.close();
};
}

// Inject the base reporter
ShutdownTestServer.$inject = ['baseReporterDecorator', 'config'];

// local "reporter" plugin
const shutdownTestServer = {
'reporter:shutdown-test-server': ['type', ShutdownTestServer]
};

module.exports = async function(config) {
testServer = new TestServer({
earlFilename: process.env.EARL
});
await testServer.start();

module.exports = function(config) {
// bundler to test: webpack, browserify
const bundler = process.env.BUNDLER || 'webpack';

const frameworks = ['mocha', 'server-side'];
const frameworks = ['mocha'];
// main bundle preprocessors
const preprocessors = ['babel'];

Expand Down Expand Up @@ -66,7 +94,8 @@ module.exports = function(config) {
'process.env.EARL': JSON.stringify(process.env.EARL),
'process.env.TESTS': JSON.stringify(process.env.TESTS),
'process.env.TEST_ENV': JSON.stringify(process.env.TEST_ENV),
'process.env.TEST_ROOT_DIR': JSON.stringify(__dirname),
'process.env.TEST_SERVER_URL': JSON.stringify(_proxyTestsPrefix),
'process.env.AUTH_TOKEN': JSON.stringify(testServer.authToken),
'process.env.VERBOSE_SKIP': JSON.stringify(process.env.VERBOSE_SKIP),
// for 'auto' test env
'process.env._TEST_ENV_ARCH': JSON.stringify(process.arch),
Expand All @@ -81,18 +110,11 @@ module.exports = function(config) {
rules: [
{
test: /\.js$/,
include: [{
// exclude node_modules by default
exclude: /(node_modules)/
}, {
// include specific packages
include: [
/(node_modules\/canonicalize)/,
/(node_modules\/lru-cache)/,
/(node_modules\/rdf-canonize)/,
/(node_modules\/yallist)/
]
}],
// avoid processing core-js
include: {
and: [/node_modules/],
not: [/core-js/]
},
use: {
loader: 'babel-loader',
options: {
Expand All @@ -101,38 +123,31 @@ module.exports = function(config) {
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: '3.9',
corejs: '3.46',
bugfixes: true,
//debug: true,
targets: {
// test with slightly looser browserslist defaults
browsers: 'defaults, > 0.25%'
browsers: 'defaults, > 0.25%, not IE 11'
}
}
]
],
plugins: [
[
'@babel/plugin-proposal-object-rest-spread',
{useBuiltIns: true}
],
'@babel/plugin-transform-modules-commonjs',
'@babel/plugin-transform-runtime'
]
}
}
}
],
noParse: [
// avoid munging internal benchmark script magic
/benchmark/
]
//noParse: [
// // avoid munging internal benchmark script magic
// /benchmark/
//]
},
node: {
Buffer: false,
process: false,
crypto: false,
setImmediate: false
output: {
globalObject: 'this'
}
},

Expand All @@ -147,7 +162,8 @@ module.exports = function(config) {
EARL: process.env.EARL,
TESTS: process.env.TESTS,
TEST_ENV: process.env.TEST_ENV,
TEST_ROOT_DIR: __dirname,
TEST_SERVER_URL: _proxyTestsPrefix,
AUTH_TOKEN: testServer.authToken,
VERBOSE_SKIP: process.env.VERBOSE_SKIP,
// for 'auto' test env
_TEST_ENV_ARCH: process.arch,
Expand All @@ -165,11 +181,20 @@ module.exports = function(config) {
]
},

// local server shutdown plugin
plugins: [
'karma-*',
shutdownTestServer
],

// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
//reporters: ['progress'],
reporters: ['mocha'],
reporters: [
'mocha',
'shutdown-test-server'
],

// web server port
port: 9876,
Expand All @@ -192,17 +217,6 @@ module.exports = function(config) {
//browsers: ['ChromeHeadless', 'Chrome', 'Firefox', 'Safari'],
browsers: ['ChromeHeadless'],

customLaunchers: {
IE9: {
base: 'IE',
'x-ua-compatible': 'IE=EmulateIE9'
},
IE8: {
base: 'IE',
'x-ua-compatible': 'IE=EmulateIE8'
}
},

// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true,
Expand All @@ -222,6 +236,8 @@ module.exports = function(config) {
},

// Proxied paths
proxies: {}
proxies: {
'/tests': testServer.url
}
});
};
53 changes: 39 additions & 14 deletions lib/fromRdf.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ api.fromRDF = async (
const nodeMap = graphMap[name];

// get subject, predicate, object
const s = quad.subject.value;
const s = _nodeId(quad.subject);
const p = quad.predicate.value;
const o = quad.object;

Expand All @@ -98,13 +98,14 @@ api.fromRDF = async (
}
const node = nodeMap[s];

const objectIsNode = o.termType.endsWith('Node');
if(objectIsNode && !(o.value in nodeMap)) {
nodeMap[o.value] = {'@id': o.value};
const objectNodeId = _nodeId(o);
const objectIsNode = !!objectNodeId;
if(objectIsNode && !(objectNodeId in nodeMap)) {
nodeMap[objectNodeId] = {'@id': objectNodeId};
}

if(p === RDF_TYPE && !useRdfType && objectIsNode) {
_addValue(node, '@type', o.value, {propertyIsArray: true});
_addValue(node, '@type', objectNodeId, {propertyIsArray: true});
continue;
}

Expand All @@ -114,9 +115,9 @@ api.fromRDF = async (
// object may be an RDF list/partial list node but we can't know easily
// until all triples are read
if(objectIsNode) {
if(o.value === RDF_NIL) {
if(objectNodeId === RDF_NIL) {
// track rdf:nil uniquely per graph
const object = nodeMap[o.value];
const object = nodeMap[objectNodeId];
if(!('usages' in object)) {
object.usages = [];
}
Expand All @@ -125,12 +126,12 @@ api.fromRDF = async (
property: p,
value
});
} else if(o.value in referencedOnce) {
} else if(objectNodeId in referencedOnce) {
// object referenced more than once
referencedOnce[o.value] = false;
referencedOnce[objectNodeId] = false;
} else {
// keep track of single reference
referencedOnce[o.value] = {
referencedOnce[objectNodeId] = {
node,
property: p,
value
Expand Down Expand Up @@ -303,8 +304,9 @@ api.fromRDF = async (
*/
function _RDFToObject(o, useNativeTypes, rdfDirection, options) {
// convert NamedNode/BlankNode object to JSON-LD
if(o.termType.endsWith('Node')) {
return {'@id': o.value};
const nodeId = _nodeId(o);
if(nodeId) {
return {'@id': nodeId};
}

// convert literal to JSON-LD
Expand Down Expand Up @@ -348,10 +350,12 @@ function _RDFToObject(o, useNativeTypes, rdfDirection, options) {
// use native types for certain xsd types
if(useNativeTypes) {
if(type === XSD_BOOLEAN) {
if(rval['@value'] === 'true') {
if(rval['@value'] === 'true' || rval['@value'] === '1') {
rval['@value'] = true;
} else if(rval['@value'] === 'false') {
} else if(rval['@value'] === 'false' || rval['@value'] === '0') {
rval['@value'] = false;
} else if(rval['@value'] === 'True' || rval['@value'] === 'False') {
rval['@type'] = type;
Copy link

@filip26 filip26 Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should simply be an else. In other words:

  • If the value is "true" (line 353), use the native true.
  • If the value is "false" (line 355), use the native false.
  • If it cannot be coerced to native types (i.e., not true, 1, false, or 0), keep it as is, that is, set the type to boolean, but do not convert the value to native types.
} else {
  rval['@type'] = type;
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about it more, you should not get '1' or '0' from RDF.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about it more, you should not get '1' or '0' from RDF.

Supporting those was part of why the newer test exists. What did you mean?

This should simply be an else.

Yes, I got confused. Made a test suite PR to help clarify and handle another similar case that this code would fail on.
w3c/json-ld-api#669

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's confusing but please keep the test as is, see w3c/json-ld-api#669 (comment).

Your fix is almost OK, just the plain else and it should work.

}
} else if(types.isNumeric(rval['@value'])) {
if(type === XSD_INTEGER) {
Expand All @@ -362,6 +366,10 @@ function _RDFToObject(o, useNativeTypes, rdfDirection, options) {
} else if(type === XSD_DOUBLE) {
rval['@value'] = parseFloat(rval['@value']);
}
} else if(type == XSD_DOUBLE) {
// if not numeric and double, include type
// occurs for valid strings such as "+INF", "-INF", and large values
rval['@type'] = type;
}
// do not add native type
if(![XSD_BOOLEAN, XSD_INTEGER, XSD_DOUBLE, XSD_STRING].includes(type)) {
Expand Down Expand Up @@ -397,3 +405,20 @@ function _RDFToObject(o, useNativeTypes, rdfDirection, options) {

return rval;
}

/**
* Return id for a term. Handles BlankNodes and NamedNodes. Adds a '_:' prefix
* for BlanksNodes.
*
* @param term a term object.
*
* @return the Node term id or null.
*/
function _nodeId(term) {
if(term.termType === 'NamedNode') {
return term.value;
} else if(term.termType === 'BlankNode') {
return '_:' + term.value;
}
return null;
}
Loading