'use strict' const ModuleError = require('module-error') const encodings = require('./lib/encodings') const { Encoding } = require('./lib/encoding') const { BufferFormat, ViewFormat, UTF8Format } = require('./lib/formats') const kFormats = Symbol('formats') const kEncodings = Symbol('encodings') const validFormats = new Set(['buffer', 'view', 'utf8']) /** @template T */ class Transcoder { /** * @param {Array<'buffer'|'view'|'utf8'>} formats */ constructor (formats) { if (!Array.isArray(formats)) { throw new TypeError("The first argument 'formats' must be an array") } else if (!formats.every(f => validFormats.has(f))) { // Note: we only only support aliases in key- and valueEncoding options (where we already did) throw new TypeError("Format must be one of 'buffer', 'view', 'utf8'") } /** @type {Map, Encoding>} */ this[kEncodings] = new Map() this[kFormats] = new Set(formats) // Register encodings (done early in order to populate encodings()) for (const k in encodings) { try { this.encoding(k) } catch (err) { /* istanbul ignore if: assertion */ if (err.code !== 'LEVEL_ENCODING_NOT_SUPPORTED') throw err } } } /** * @returns {Array>} */ encodings () { return Array.from(new Set(this[kEncodings].values())) } /** * @param {string|MixedEncoding} encoding * @returns {Encoding} */ encoding (encoding) { let resolved = this[kEncodings].get(encoding) if (resolved === undefined) { if (typeof encoding === 'string' && encoding !== '') { resolved = lookup[encoding] if (!resolved) { throw new ModuleError(`Encoding '${encoding}' is not found`, { code: 'LEVEL_ENCODING_NOT_FOUND' }) } } else if (typeof encoding !== 'object' || encoding === null) { throw new TypeError("First argument 'encoding' must be a string or object") } else { resolved = from(encoding) } const { name, format } = resolved if (!this[kFormats].has(format)) { if (this[kFormats].has('view')) { resolved = resolved.createViewTranscoder() } else if (this[kFormats].has('buffer')) { resolved = resolved.createBufferTranscoder() } else if (this[kFormats].has('utf8')) { resolved = resolved.createUTF8Transcoder() } else { throw new ModuleError(`Encoding '${name}' cannot be transcoded`, { code: 'LEVEL_ENCODING_NOT_SUPPORTED' }) } } for (const k of [encoding, name, resolved.name, resolved.commonName]) { this[kEncodings].set(k, resolved) } } return resolved } } exports.Transcoder = Transcoder /** * @param {MixedEncoding} options * @returns {Encoding} */ function from (options) { if (options instanceof Encoding) { return options } // Loosely typed for ecosystem compatibility const maybeType = 'type' in options && typeof options.type === 'string' ? options.type : undefined const name = options.name || maybeType || `anonymous-${anonymousCount++}` switch (detectFormat(options)) { case 'view': return new ViewFormat({ ...options, name }) case 'utf8': return new UTF8Format({ ...options, name }) case 'buffer': return new BufferFormat({ ...options, name }) default: { throw new TypeError("Format must be one of 'buffer', 'view', 'utf8'") } } } /** * If format is not provided, fallback to detecting `level-codec` * or `multiformats` encodings, else assume a format of buffer. * @param {MixedEncoding} options * @returns {string} */ function detectFormat (options) { if ('format' in options && options.format !== undefined) { return options.format } else if ('buffer' in options && typeof options.buffer === 'boolean') { return options.buffer ? 'buffer' : 'utf8' // level-codec } else if ('code' in options && Number.isInteger(options.code)) { return 'view' // multiformats } else { return 'buffer' } } /** * @typedef {import('./lib/encoding').MixedEncoding} MixedEncoding * @template TIn, TFormat, TOut */ /** * @type {Object.>} */ const aliases = { binary: encodings.buffer, 'utf-8': encodings.utf8 } /** * @type {Object.>} */ const lookup = { ...encodings, ...aliases } let anonymousCount = 0