Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
# @digitalbazaar/cborld ChangeLog

## 8.0.0 - 2025-04-dd

### Changed
- **BREAKING**: The default `format` for `encode()` is now "cbor-ld-1.0". To
generate output using pre-1.0 CBOR-LD tags (legacy tags), pass a different
format (e.g, "legacy-range" or "legacy-singleton").
- **BREAKING**: The `registryEntryId` parameter for `encode()` can no longer
be the string "legacy"; it can only be a number. To output pre-1.0 CBOR-LD
tags 1280/1281 (0x0500/0x0501) pass "legacy-singleton" for `format`.

### Removed
- **BREAKING**: Remove `typeTable` parameter from `encode()` and `decode()`;
use `typeTableLoader` instead.

## 7.3.0 - 2025-04-24

### Added
- Added new tag system for use with a single CBOR tag (0xcb1d). The tagged
- Add a new tag system for use with a single CBOR tag (0xcb1d). The tagged
item is always an array of two elements. The first element is a CBOR integer
representation of the registry entry ID for the payload, and the second
element is the encoded JSON-LD. To use the new mode when encoding, pass
Expand Down
86 changes: 41 additions & 45 deletions lib/decode.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,37 @@ import {parse} from './parser.js';
* decode.
* @param {documentLoaderFunction} options.documentLoader - The document loader
* to use when resolving JSON-LD Context URLs.
* @param {Map} [options.typeTable] - A map of possible value types, including
* `context`, `url`, `none`, and any JSON-LD type, each of which maps to
* another map of values of that type to their associated CBOR-LD integer
* values.
* @param {Function} [options.typeTableLoader] - The typeTable loader to use to
* resolve a registryEntryId to a typeTable.
* @param {Function} [options.typeTableLoader] - The `typeTable` loader to use
* to resolve a `registryEntryId` to a `typeTable`. A `typeTable` is a Map of
* possible value types, including `context`, `url`, `none`, and any JSON-LD
* type, each of which maps to another Map of values of that type to their
* associated CBOR-LD integer values.
* @param {diagnosticFunction} [options.diagnose] - A function that, if
* provided, is called with diagnostic information.
* @param {Map} [options.appContextMap] - A map of context string values
* to their associated CBOR-LD integer values. For use with legacy
* cborldBytes.
* @param {Map} [options.appContextMap] - For use with "legacy-singleton"
* formatted inputs only; a map of context string values to their associated
* CBOR-LD integer values.
* @param {*} [options.typeTable] - NOT permitted; use `typeTableLoader`.
*
* @returns {Promise<object>} - The decoded JSON-LD Document.
*/
export async function decode({
cborldBytes,
documentLoader,
typeTable,
typeTableLoader,
diagnose,
appContextMap = new Map(),
}) {
// for "legacy-singleton" format only
appContextMap,
// no longer permitted
typeTable
} = {}) {
if(!(cborldBytes instanceof Uint8Array)) {
throw new TypeError('"cborldBytes" must be a Uint8Array.');
}
// throw on `typeTable` param which is no longer permitted
if(typeTable !== undefined) {
throw new TypeError('"typeTable" is not allowed; use "typeTableLoader".');
}

// parse CBOR-LD into a header and payload
const {header, payload} = parse({cborldBytes});
Expand All @@ -55,37 +61,42 @@ export async function decode({
diagnose(inspect(input, {depth: null, colors: true}));
}

// lookup typeTable by id if needed
// get `typeTable` by `registryEntryId` / `format`
const {format, registryEntryId} = header;
if(format === 'legacy-singleton') {
// generate legacy type table
typeTable = createLegacyTypeTable({typeTable, appContextMap});
typeTable = createLegacyTypeTable({appContextMap});
} else if(registryEntryId === 1) {
// use default table (empty) for registry entry ID `1`
typeTable = createTypeTable({typeTable});
} else {
if(typeTable && typeTableLoader) {
throw new TypeError('Use either "typeTable" or "typeTableLoader".');
}
if(!typeTable && typeTableLoader) {
if(registryEntryId === 1) {
// use default table (empty) for registry entry ID `1`
typeTable = createTypeTable({typeTable});
} else {
typeTable = await typeTableLoader({registryEntryId});
}
if(typeof typeTableLoader === 'function') {
typeTable = await typeTableLoader({registryEntryId});
}
if(!typeTable) {
if(!(typeTable instanceof Map)) {
throw new CborldError(
'ERR_NO_TYPETABLE',
'"typeTable" not provided or found for registryEntryId ' +
'"typeTable" not found for "registryEntryId" ' +
`"${registryEntryId}".`);
}
// check to make sure unsupported types not present in `typeTable`
if(typeTable.has('http://www.w3.org/2001/XMLSchema#integer') ||
typeTable.has('http://www.w3.org/2001/XMLSchema#double') ||
typeTable.has('http://www.w3.org/2001/XMLSchema#boolean')) {
throw new CborldError(
'ERR_UNSUPPORTED_LITERAL_TYPE',
'"typeTable" must not contain XSD integers, doubles, or booleans.');
}
// normalize type table
typeTable = createTypeTable({typeTable});
}

const converter = _createConverter({
format,
typeTable,
documentLoader
const converter = new Converter({
// decompress CBOR-LD => JSON-LD
strategy: new Decompressor({typeTable}),
documentLoader,
// FIXME: try to eliminate need for legacy flag
legacy: format === 'legacy-singleton'
});
const output = await converter.convert({input});
if(diagnose) {
Expand All @@ -95,21 +106,6 @@ export async function decode({
return output;
}

// FIXME: consider making static method on `Converter`
function _createConverter({
format,
typeTable,
documentLoader
}) {
return new Converter({
// decompress CBOR-LD => JSON-LD
strategy: new Decompressor({typeTable}),
documentLoader,
// FIXME: try to eliminate need for legacy flag
legacy: format === 'legacy-singleton'
});
}

/**
* A diagnostic function that is called with diagnostic information. Typically
* set to `console.log` when debugging.
Expand Down
154 changes: 67 additions & 87 deletions lib/encode.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,55 +27,42 @@ const typeEncoders = {
* CBOR-LD bytes.
* @param {documentLoaderFunction} options.documentLoader - The document loader
* to use when resolving JSON-LD Context URLs.
* @param {number|string} [options.registryEntryId='legacy'] - The registry
* @param {string} [options.format='cbor-ld-1.0'] - The CBOR-LD output format
* to use; this will default to `cbor-ld-1.0` to use CBOR-LD 1.0 tag
* `0xcb1d` (51997); to create output with a pre-1.0 CBOR-LD tag, then
* 'legacy-range' can be passed to use tags `0x0600-0x06ff` (1526-1791) and
* 'legacy-singleton' can be passed to use tags `0x0500-0x0501`.
* @param {number|string} [options.registryEntryId] - The registry
* entry ID for the registry entry associated with the resulting CBOR-LD
* payload. For legacy singleton support, use registryEntryId = 'legacy'.
* @param {string} [options.format] - The CBOR-LD output format to use; this
* SHOULD be set by the caller to `cbor-ld-1.0` to use CBOR-LD 1.0 tag
* `0xcb1d` (51997); if this is not provided, then for backwards
* compatibility purposes, `registryEntryId` will control which legacy tags
* are used (if `registryEntryId` is a number, then the output will use tags
* `0x0600-0x06ff` (1526-1791) and if it is the string 'legacy' then it will
* use tags `0x0500-0x0501` depending on `compressionMode`).
* @param {Map} [options.typeTable] - A map of possible value types, including
* `context`, `url`, `none`, and any JSON-LD type, each of which maps to
* another map of values of that type to their associated CBOR-LD integer
* values; can only be used if a number if provided for `registryEntryId`.
* @param {Function} [options.typeTableLoader] - The loader to use to resolve
* a `registryEntryId` to a `typeTable`; can only be used if a number is
* provided for `registryEntryId`.
* payload.
* @param {Function} [options.typeTableLoader] - The `typeTable` loader to use
* to resolve `registryEntryId` to a `typeTable`. A `typeTable` is a Map of
* possible value types, including `context`, `url`, `none`, and any JSON-LD
* type, each of which maps to another Map of values of that type to their
* associated CBOR-LD integer values.
* @param {diagnosticFunction} [options.diagnose] - A function that, if
* provided, is called with diagnostic information.
* @param {Map} [options.appContextMap] - For use with the 'legacy' value of
* `registryEntryId`.
* @param {number} [options.compressionMode] - For use with the 'legacy' value
* of `registryEntryId`.
* @param {Map} [options.appContextMap] - Only for use with the
* 'legacy-singleton' format.
* @param {number} [options.compressionMode] - Only for use with the
* 'legacy-singleton' format.
* @param {*} [options.typeTable] - NOT permitted; use `typeTableLoader`.
*
* @returns {Promise<Uint8Array>} - The encoded CBOR-LD bytes.
*/
export async function encode({
jsonldDocument,
documentLoader,
format,
registryEntryId = 'legacy',
typeTable,
format = 'cbor-ld-1.0',
registryEntryId,
typeTableLoader,
diagnose,
// for "legacy-singleton" format only
appContextMap,
compressionMode
compressionMode,
// no longer permitted
typeTable
} = {}) {
// validate that an acceptable value for `registryEntryId` was passed
if(!((typeof registryEntryId === 'number' && registryEntryId >= 0) ||
registryEntryId === 'legacy')) {
throw new TypeError(
'"registryEntryId" must be either an integer or `legacy`.');
}

// if `format` is undefined, determine it based on other parameters...
if(format === undefined) {
format = registryEntryId === 'legacy' ? 'legacy-singleton' : 'legacy-range';
}

// validate `format`
if(!(format === 'cbor-ld-1.0' || format === 'legacy-range' ||
format === 'legacy-singleton')) {
Expand All @@ -85,22 +72,21 @@ export async function encode({
'"legacy-range" (tags 0x0600-0x06ff = 1536-1791) or ' +
'"legacy-singleton" (tags 0x0500-0x0501 = 1280-1281).');
}
// throw on `typeTable` param which is no longer permitted
if(typeTable !== undefined) {
throw new TypeError('"typeTable" is not allowed; use "typeTableLoader".');
}

// validate parameter combinations
let compressPayload;
if(format === 'legacy-singleton') {
if(registryEntryId !== 'legacy') {
throw new TypeError(
'"registryEntryId" must be set to "legacy" to use ' +
'the "legacy-singleton" format');
}
if(typeTable !== undefined) {
if(registryEntryId !== undefined) {
throw new TypeError(
'"typeTable" must only be passed when "registryEntryId" is a number.');
'"registryEntryId" must not be used with format "legacy-singleton".');
}
if(typeTableLoader !== undefined) {
throw new TypeError(
'"typeTable" must only be passed when "registryEntryId" is a number.');
'"typeTableLoader" must not be used with format "legacy-singleton".');
}

// default compression mode to `1`
Expand All @@ -115,47 +101,45 @@ export async function encode({
// generate legacy type table
typeTable = createLegacyTypeTable({typeTable, appContextMap});
} else {
// validate that an acceptable value for `registryEntryId` was passed
if(!(typeof registryEntryId === 'number' && registryEntryId >= 0)) {
throw new TypeError('"registryEntryId" must be a non-negative integer.');
}

if(appContextMap !== undefined) {
throw new TypeError(
'"appContextMap" must only be passed in "legacy" mode.');
'"appContextMap" must only be used with format "legacy-singleton".');
}
if(compressionMode !== undefined) {
throw new TypeError(
'"compressionMode" must only be passed in "legacy" mode.');
}
if(typeTable !== undefined) {
if(typeTableLoader !== undefined) {
throw new TypeError('Use either "typeTable" or "typeTableLoader".');
}
if(!(typeTable instanceof Map)) {
throw new TypeError('"typeTable" must be a Map.');
}
} else if(registryEntryId === 1) {
// use default table (empty) for registry entry ID `1`
typeTable = createTypeTable({typeTable});
} else if(typeTableLoader) {
if(typeof typeTableLoader !== 'function') {
throw new TypeError('"typeTableLoader" must be a function.');
}
typeTable = await typeTableLoader({registryEntryId});
'"compressionMode" must only be used with format "legacy-singleton".');
}
if(registryEntryId !== 0) {
if(!typeTable) {
throw new CborldError(
'ERR_NO_TYPETABLE',
'"typeTable" not provided or found for "registryEntryId" ' +
`"${registryEntryId}".`);
}
// check to make sure unsupported types not present in `typeTable`
if(typeTable.has('http://www.w3.org/2001/XMLSchema#integer') ||
typeTable.has('http://www.w3.org/2001/XMLSchema#double') ||
typeTable.has('http://www.w3.org/2001/XMLSchema#boolean')) {
throw new CborldError(
'ERR_UNSUPPORTED_LITERAL_TYPE',
'"typeTable" must not contain XSD integers, doubles, or booleans.');
if(registryEntryId === 1) {
// use default table (empty) for registry entry ID `1`
typeTable = createTypeTable({typeTable});
} else {
if(typeof typeTableLoader !== 'function') {
throw new TypeError('"typeTableLoader" must be a function.');
}
typeTable = await typeTableLoader({registryEntryId});
if(!(typeTable instanceof Map)) {
throw new CborldError(
'ERR_NO_TYPETABLE',
'"typeTable" not found for "registryEntryId" ' +
`"${registryEntryId}".`);
}
// check to make sure unsupported types not present in `typeTable`
if(typeTable.has('http://www.w3.org/2001/XMLSchema#integer') ||
typeTable.has('http://www.w3.org/2001/XMLSchema#double') ||
typeTable.has('http://www.w3.org/2001/XMLSchema#boolean')) {
throw new CborldError(
'ERR_UNSUPPORTED_LITERAL_TYPE',
'"typeTable" must not contain XSD integers, doubles, or booleans.');
}
// normalize type table
typeTable = createTypeTable({typeTable});
}
// normalize type table
typeTable = createTypeTable({typeTable});
}
// any registry entry ID other than zero uses compression by default
compressPayload = registryEntryId !== 0;
Expand All @@ -167,7 +151,12 @@ export async function encode({
// output uncompressed CBOR-LD
suffix = cborg.encode(jsonldDocument);
} else {
const converter = _createConverter({format, typeTable, documentLoader});
const converter = new Converter({
// compress JSON-LD => CBOR-LD
strategy: new Compressor({typeTable}),
documentLoader,
legacy: format === 'legacy-singleton'
});
const output = await converter.convert({input: jsonldDocument});
if(diagnose) {
diagnose('Diagnostic CBOR-LD compression transform map(s):');
Expand Down Expand Up @@ -235,15 +224,6 @@ function _getPrefix({format, compressionMode, registryEntryId}) {
]);
}

function _createConverter({format, typeTable, documentLoader}) {
return new Converter({
// compress JSON-LD => CBOR-LD
strategy: new Compressor({typeTable}),
documentLoader,
legacy: format === 'legacy-singleton'
});
}

/**
* A diagnostic function that is called with diagnostic information. Typically
* set to `console.log` when debugging.
Expand Down
Loading