|
1 | 1 | 'use strict' |
2 | 2 |
|
| 3 | +const ModuleError = require('module-error') |
3 | 4 | const encodings = require('./lib/encodings') |
4 | | -const rangeOptions = new Set(['lt', 'gt', 'lte', 'gte']) |
5 | | - |
6 | | -module.exports = Codec |
7 | | - |
8 | | -function Codec (opts) { |
9 | | - if (!(this instanceof Codec)) { |
10 | | - return new Codec(opts) |
11 | | - } |
12 | | - this.opts = opts || {} |
13 | | - this.encodings = encodings |
14 | | -} |
15 | | - |
16 | | -Codec.prototype._encoding = function (encoding) { |
17 | | - if (typeof encoding === 'string') encoding = encodings[encoding] |
18 | | - if (!encoding) encoding = encodings.id |
19 | | - return encoding |
20 | | -} |
21 | | - |
22 | | -Codec.prototype._keyEncoding = function (opts, batchOpts) { |
23 | | - return this._encoding((batchOpts && batchOpts.keyEncoding) || |
24 | | - (opts && opts.keyEncoding) || |
25 | | - this.opts.keyEncoding) |
26 | | -} |
27 | | - |
28 | | -Codec.prototype._valueEncoding = function (opts, batchOpts) { |
29 | | - return this._encoding((batchOpts && (batchOpts.valueEncoding || batchOpts.encoding)) || |
30 | | - (opts && (opts.valueEncoding || opts.encoding)) || |
31 | | - (this.opts.valueEncoding || this.opts.encoding)) |
32 | | -} |
33 | | - |
34 | | -Codec.prototype.encodeKey = function (key, opts, batchOpts) { |
35 | | - return this._keyEncoding(opts, batchOpts).encode(key) |
36 | | -} |
37 | | - |
38 | | -Codec.prototype.encodeValue = function (value, opts, batchOpts) { |
39 | | - return this._valueEncoding(opts, batchOpts).encode(value) |
40 | | -} |
41 | | - |
42 | | -Codec.prototype.decodeKey = function (key, opts) { |
43 | | - return this._keyEncoding(opts).decode(key) |
44 | | -} |
45 | | - |
46 | | -Codec.prototype.decodeValue = function (value, opts) { |
47 | | - return this._valueEncoding(opts).decode(value) |
48 | | -} |
49 | | - |
50 | | -Codec.prototype.encodeBatch = function (ops, opts) { |
51 | | - return ops.map((_op) => { |
52 | | - const op = { |
53 | | - type: _op.type, |
54 | | - key: this.encodeKey(_op.key, opts, _op) |
| 5 | +const { Encoding } = require('./lib/encoding') |
| 6 | +const { BufferFormat, ViewFormat, UTF8Format, NativeFormat } = require('./lib/formats') |
| 7 | + |
| 8 | +const kFormats = Symbol('formats') |
| 9 | +const kEncodings = Symbol('encodings') |
| 10 | + |
| 11 | +/** @template T */ |
| 12 | +class Transcoder { |
| 13 | + /** |
| 14 | + * @param {Iterable<string>} formats |
| 15 | + */ |
| 16 | + constructor (formats) { |
| 17 | + /** @type {Map<string|Encoding<any, any, any>|EncodingOptions<any, any, any>, Encoding<any, any, any>>} */ |
| 18 | + this[kEncodings] = new Map() |
| 19 | + this[kFormats] = new Set(formats) |
| 20 | + |
| 21 | + // Only support aliases in key- and valueEncoding options (where we already did) |
| 22 | + for (const [alias, { type }] of Object.entries(aliases)) { |
| 23 | + if (this[kFormats].has(alias)) { |
| 24 | + throw new ModuleError(`The '${alias}' alias is not supported here; use '${type}' instead`, { |
| 25 | + code: 'LEVEL_ENCODING_NOT_SUPPORTED' |
| 26 | + }) |
| 27 | + } |
55 | 28 | } |
56 | | - if (this.keyAsBuffer(opts, _op)) op.keyEncoding = 'binary' |
57 | | - if (_op.prefix) op.prefix = _op.prefix |
58 | | - if ('value' in _op) { |
59 | | - op.value = this.encodeValue(_op.value, opts, _op) |
60 | | - if (this.valueAsBuffer(opts, _op)) op.valueEncoding = 'binary' |
| 29 | + |
| 30 | + // Register encodings (done early in order to populate types()) |
| 31 | + for (const k in encodings) { |
| 32 | + try { |
| 33 | + this.encoding(k) |
| 34 | + } catch (err) { |
| 35 | + if (err.code !== 'LEVEL_ENCODING_NOT_SUPPORTED') throw err |
| 36 | + } |
61 | 37 | } |
62 | | - return op |
63 | | - }) |
64 | | -} |
| 38 | + } |
65 | 39 |
|
66 | | -Codec.prototype.encodeLtgt = function (ltgt) { |
67 | | - const ret = {} |
| 40 | + types () { |
| 41 | + const types = new Set() |
68 | 42 |
|
69 | | - for (const key of Object.keys(ltgt)) { |
70 | | - if (key === 'start' || key === 'end') { |
71 | | - throw new Error('Legacy range options ("start" and "end") have been removed') |
| 43 | + for (const encoding of this[kEncodings].values()) { |
| 44 | + const type = encoding.type.split('+')[0] |
| 45 | + if (type) types.add(type) |
72 | 46 | } |
73 | 47 |
|
74 | | - ret[key] = rangeOptions.has(key) |
75 | | - ? this.encodeKey(ltgt[key], ltgt) |
76 | | - : ltgt[key] |
| 48 | + return Array.from(types) |
77 | 49 | } |
78 | 50 |
|
79 | | - return ret |
80 | | -} |
| 51 | + // TODO: document that we don't fallback to 'id' anymore if encoding is not found |
| 52 | + /** |
| 53 | + * @param {string|Encoding<any, any, any>|EncodingOptions<any, any, any>} encoding |
| 54 | + * @returns {Encoding<any, T, any>} |
| 55 | + */ |
| 56 | + encoding (encoding) { |
| 57 | + let resolved = this[kEncodings].get(encoding) |
| 58 | + |
| 59 | + if (resolved === undefined) { |
| 60 | + if (typeof encoding === 'string' && encoding !== '') { |
| 61 | + resolved = lookup[encoding] |
| 62 | + |
| 63 | + if (!resolved) { |
| 64 | + throw new ModuleError( |
| 65 | + `Encoding '${encoding}' is not found`, |
| 66 | + { code: 'LEVEL_ENCODING_NOT_FOUND' } |
| 67 | + ) |
| 68 | + } |
| 69 | + } else if (typeof encoding !== 'object' || encoding === null) { |
| 70 | + throw new TypeError('Encoding must be a string or object') |
| 71 | + } else if (encoding instanceof Encoding) { |
| 72 | + resolved = encoding |
| 73 | + } else if (encoding.format === 'view') { |
| 74 | + resolved = new ViewFormat(encoding) |
| 75 | + } else if (encoding.format === 'utf8' || encoding.buffer === false) { |
| 76 | + resolved = new UTF8Format(encoding) |
| 77 | + } else { |
| 78 | + resolved = new BufferFormat(encoding) |
| 79 | + } |
81 | 80 |
|
82 | | -Codec.prototype.createStreamDecoder = function (opts) { |
83 | | - if (opts.keys && opts.values) { |
84 | | - return (key, value) => { |
85 | | - return { |
86 | | - key: this.decodeKey(key, opts), |
87 | | - value: this.decodeValue(value, opts) |
| 81 | + const { type, idempotent, format } = resolved |
| 82 | + |
| 83 | + if (this[kFormats].has(type)) { |
| 84 | + // If idempotent, run data through it to normalize |
| 85 | + if (!idempotent) resolved = new NativeFormat(type) |
| 86 | + } else if (!this[kFormats].has(format)) { |
| 87 | + if (this[kFormats].has('view')) { |
| 88 | + resolved = resolved.transcode('view') |
| 89 | + } else if (this[kFormats].has('buffer')) { |
| 90 | + resolved = resolved.transcode('buffer') |
| 91 | + } else { |
| 92 | + throw new ModuleError( |
| 93 | + `Encoding '${type}' or '${format}' is not supported`, |
| 94 | + { code: 'LEVEL_ENCODING_NOT_SUPPORTED' } |
| 95 | + ) |
| 96 | + } |
| 97 | + } |
| 98 | + |
| 99 | + for (const k of [encoding, type, resolved.type]) { |
| 100 | + if (k) this[kEncodings].set(k, resolved) |
88 | 101 | } |
89 | 102 | } |
90 | | - } else if (opts.keys) { |
91 | | - return (key) => { |
92 | | - return this.decodeKey(key, opts) |
93 | | - } |
94 | | - } else if (opts.values) { |
95 | | - return (_, value) => { |
96 | | - return this.decodeValue(value, opts) |
97 | | - } |
98 | | - } else { |
99 | | - return function () {} |
| 103 | + |
| 104 | + return resolved |
100 | 105 | } |
101 | 106 | } |
102 | 107 |
|
103 | | -Codec.prototype.keyAsBuffer = function (opts) { |
104 | | - return this._keyEncoding(opts).buffer |
| 108 | +module.exports = Transcoder |
| 109 | + |
| 110 | +/** |
| 111 | + * @typedef {import('./lib/encoding').EncodingOptions<TIn,TFormat,TOut>} EncodingOptions |
| 112 | + * @template TIn, TFormat, TOut |
| 113 | + */ |
| 114 | + |
| 115 | +/** |
| 116 | + * @type {Object.<string, Encoding<any, any, any>>} |
| 117 | + */ |
| 118 | +const aliases = { |
| 119 | + binary: encodings.buffer, |
| 120 | + 'utf-8': encodings.utf8, |
| 121 | + none: encodings.id |
105 | 122 | } |
106 | 123 |
|
107 | | -Codec.prototype.valueAsBuffer = function (opts) { |
108 | | - return this._valueEncoding(opts).buffer |
| 124 | +/** |
| 125 | + * @type {Object.<string, Encoding<any, any, any>>} |
| 126 | + */ |
| 127 | +const lookup = { |
| 128 | + ...encodings, |
| 129 | + ...aliases |
109 | 130 | } |
0 commit comments