Skip to content

Commit ae86690

Browse files
committed
Refactor encoding interfaces and add TS utilities
1 parent cc2db75 commit ae86690

18 files changed

+294
-156
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
Create a transcoder, passing a desired format:
1717

1818
```js
19-
const Transcoder = require('level-transcoder')
19+
const { Transcoder } = require('level-transcoder')
2020

2121
const transcoder1 = new Transcoder(['view'])
2222
const transcoder2 = new Transcoder(['buffer'])
@@ -163,7 +163,7 @@ Name of the (lower-level) encoding used by the return value of `encode()`. One o
163163
Custom encodings must follow the following interface:
164164

165165
```ts
166-
interface EncodingOptions<TIn, TFormat, TOut> {
166+
interface IEncoding<TIn, TFormat, TOut> {
167167
name: string
168168
format: 'buffer' | 'view' | 'utf8'
169169
encode: (data: TIn) => TFormat

index.d.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { Encoding, EncodingOptions } from './lib/encoding'
1+
import { Encoding, MixedEncoding, KnownEncoding, KnownEncodingName } from './lib/encoding'
22

3-
declare class Transcoder<T = any> {
3+
export class Transcoder<T = any> {
44
/**
55
* Create a Transcoder.
66
* @param formats Formats supported by consumer.
@@ -17,16 +17,11 @@ declare class Transcoder<T = any> {
1717
* @param encoding Named encoding or encoding object.
1818
*/
1919
encoding<TIn, TFormat, TOut> (
20-
encoding: Encoding<TIn, TFormat, TOut>|EncodingOptions<TIn, TFormat, TOut>
20+
encoding: MixedEncoding<TIn, TFormat, TOut>
2121
): Encoding<TIn, T, TOut>
2222

23-
encoding (encoding: 'utf8'): Encoding<string | Buffer | Uint8Array, T, string>
24-
encoding (encoding: 'buffer'): Encoding<Buffer | Uint8Array | string, T, Buffer>
25-
encoding (encoding: 'view'): Encoding<Uint8Array | string, T, Uint8Array>
26-
encoding (encoding: 'json'): Encoding<any, T, any>
27-
encoding (encoding: 'hex'): Encoding<Buffer | string, T, string>
28-
encoding (encoding: 'base64'): Encoding<Buffer | string, T, string>
23+
encoding<N extends KnownEncodingName> (encoding: N): KnownEncoding<N, T>
2924
encoding (encoding: string): Encoding<any, T, any>
3025
}
3126

32-
export = Transcoder
27+
export * from './lib/encoding'

index.js

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class Transcoder {
2222
throw new TypeError("Format must be one of 'buffer', 'view', 'utf8'")
2323
}
2424

25-
/** @type {Map<string|Encoding<any, any, any>|EncodingOptions<any, any, any>, Encoding<any, any, any>>} */
25+
/** @type {Map<string|MixedEncoding<any, any, any>, Encoding<any, any, any>>} */
2626
this[kEncodings] = new Map()
2727
this[kFormats] = new Set(formats)
2828

@@ -45,7 +45,7 @@ class Transcoder {
4545
}
4646

4747
/**
48-
* @param {string|Encoding<any, any, any>|EncodingOptions<any, any, any>} encoding
48+
* @param {string|MixedEncoding<any, any, any>} encoding
4949
* @returns {Encoding<any, T, any>}
5050
*/
5151
encoding (encoding) {
@@ -62,8 +62,6 @@ class Transcoder {
6262
}
6363
} else if (typeof encoding !== 'object' || encoding === null) {
6464
throw new TypeError("First argument 'encoding' must be a string or object")
65-
} else if (encoding instanceof Encoding) {
66-
resolved = encoding
6765
} else {
6866
resolved = from(encoding)
6967
}
@@ -75,8 +73,10 @@ class Transcoder {
7573
resolved = resolved.createViewTranscoder()
7674
} else if (this[kFormats].has('buffer')) {
7775
resolved = resolved.createBufferTranscoder()
76+
} else if (this[kFormats].has('utf8')) {
77+
resolved = resolved.createUTF8Transcoder()
7878
} else {
79-
throw new ModuleError(`Encoding '${name}' cannot be transcoded to 'utf8'`, {
79+
throw new ModuleError(`Encoding '${name}' cannot be transcoded`, {
8080
code: 'LEVEL_ENCODING_NOT_SUPPORTED'
8181
})
8282
}
@@ -91,19 +91,25 @@ class Transcoder {
9191
}
9292
}
9393

94-
module.exports = Transcoder
94+
exports.Transcoder = Transcoder
9595

9696
/**
97-
* @param {EncodingOptions<any, any, any>} options
97+
* @param {MixedEncoding<any, any, any>} options
9898
* @returns {Encoding<any, any, any>}
9999
*/
100100
function from (options) {
101-
const format = detectFormat(options)
101+
if (options instanceof Encoding) {
102+
return options
103+
}
104+
105+
// Loosely typed for ecosystem compatibility
106+
const maybeType = 'type' in options && typeof options.type === 'string' ? options.type : undefined
107+
const name = options.name || maybeType || `anonymous-${anonymousCount++}`
102108

103-
switch (format) {
104-
case 'view': return new ViewFormat(options)
105-
case 'utf8': return new UTF8Format(options)
106-
case 'buffer': return new BufferFormat(options)
109+
switch (detectFormat(options)) {
110+
case 'view': return new ViewFormat({ ...options, name })
111+
case 'utf8': return new UTF8Format({ ...options, name })
112+
case 'buffer': return new BufferFormat({ ...options, name })
107113
default: {
108114
throw new TypeError("Format must be one of 'buffer', 'view', 'utf8'")
109115
}
@@ -113,23 +119,23 @@ function from (options) {
113119
/**
114120
* If format is not provided, fallback to detecting `level-codec`
115121
* or `multiformats` encodings, else assume a format of buffer.
116-
* @param {EncodingOptions<any, any, any>} options
122+
* @param {MixedEncoding<any, any, any>} options
117123
* @returns {string}
118124
*/
119-
function detectFormat ({ format, buffer, code }) {
120-
if (format !== undefined) {
121-
return format
122-
} else if (typeof buffer === 'boolean') {
123-
return buffer ? 'buffer' : 'utf8' // level-codec
124-
} else if (Number.isInteger(code)) {
125+
function detectFormat (options) {
126+
if ('format' in options && options.format !== undefined) {
127+
return options.format
128+
} else if ('buffer' in options && typeof options.buffer === 'boolean') {
129+
return options.buffer ? 'buffer' : 'utf8' // level-codec
130+
} else if ('code' in options && Number.isInteger(options.code)) {
125131
return 'view' // multiformats
126132
} else {
127133
return 'buffer'
128134
}
129135
}
130136

131137
/**
132-
* @typedef {import('./lib/encoding').EncodingOptions<TIn,TFormat,TOut>} EncodingOptions
138+
* @typedef {import('./lib/encoding').MixedEncoding<TIn,TFormat,TOut>} MixedEncoding
133139
* @template TIn, TFormat, TOut
134140
*/
135141

@@ -148,3 +154,5 @@ const lookup = {
148154
...encodings,
149155
...aliases
150156
}
157+
158+
let anonymousCount = 0

lib/encoding.d.ts

Lines changed: 99 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-
import { BufferFormat, ViewFormat } from './formats'
1+
import { BufferFormat, ViewFormat, UTF8Format } from './formats'
22

33
/**
44
* Encodes {@link TIn} to {@link TFormat} and decodes
55
* {@link TFormat} to {@link TOut}.
66
*/
7-
export abstract class Encoding<TIn, TFormat, TOut> {
8-
constructor (options: EncodingOptions<TIn, TFormat, TOut>)
7+
export abstract class Encoding<TIn, TFormat, TOut> implements IEncoding<TIn, TFormat, TOut> {
8+
constructor (options: IEncoding<TIn, TFormat, TOut>)
99

10-
/** Encode data. */
1110
encode: (data: TIn) => TFormat
12-
13-
/** Decode data. */
1411
decode: (data: TFormat) => TOut
15-
16-
/** Unique name. */
1712
name: string
13+
format: 'buffer' | 'view' | 'utf8'
14+
createViewTranscoder (): ViewFormat<TIn, TOut>
15+
createBufferTranscoder (): BufferFormat<TIn, TOut>
16+
createUTF8Transcoder (): UTF8Format<TIn, TOut>
1817

1918
/**
2019
* Common name, computed from {@link name}. If this encoding is a
@@ -23,53 +22,65 @@ export abstract class Encoding<TIn, TFormat, TOut> {
2322
* will equal {@link commonName}.
2423
*/
2524
get commonName (): string
25+
}
26+
27+
export interface IEncoding<TIn, TFormat, TOut> {
28+
/**
29+
* Encode data.
30+
*/
31+
encode: (data: TIn) => TFormat
32+
33+
/**
34+
* Decode data.
35+
*/
36+
decode: (data: TFormat) => TOut
2637

2738
/**
28-
* Name of the (lower-level) encoding used by the return value of
39+
* Unique name.
40+
*/
41+
name: string
42+
43+
/**
44+
* The name of the (lower-level) encoding used by the return value of
2945
* {@link encode}. One of 'buffer', 'view', 'utf8'.
3046
*/
3147
format: 'buffer' | 'view' | 'utf8'
3248

3349
/**
34-
* Create a new encoding that transcodes {@link TFormat} to a view.
50+
* Create a new encoding that transcodes {@link TFormat} from / to a view.
3551
*/
36-
createViewTranscoder (): ViewFormat<TIn, TOut>
52+
createViewTranscoder?: (() => ViewFormat<TIn, TOut>) | undefined
3753

3854
/**
39-
* Create a new encoding that transcodes {@link TFormat} to a buffer.
55+
* Create a new encoding that transcodes {@link TFormat} from / to a buffer.
4056
*/
41-
createBufferTranscoder (): BufferFormat<TIn, TOut>
57+
createBufferTranscoder?: (() => BufferFormat<TIn, TOut>) | undefined
58+
59+
/**
60+
* Create a new encoding that transcodes {@link TFormat} from / to a UTF-8 string.
61+
*/
62+
createUTF8Transcoder?: (() => UTF8Format<TIn, TOut>) | undefined
4263
}
4364

44-
// TODO: split into multiple interfaces (and then union those) in order to
45-
// separate the main level-transcoder interface from compatibility options.
46-
export interface EncodingOptions<TIn, TFormat, TOut> {
65+
export interface IExternalEncoding<TIn, TFormat, TOut> {
4766
/**
4867
* Encode data.
4968
*/
50-
encode?: ((data: TIn) => TFormat) | undefined
69+
encode: (data: TIn) => TFormat
5170

5271
/**
5372
* Decode data.
5473
*/
55-
decode?: ((data: TFormat) => TOut) | undefined
74+
decode: (data: TFormat) => TOut
5675

5776
/**
5877
* Unique name.
5978
*/
6079
name?: string | undefined
6180

62-
/**
63-
* The name of the (lower-level) encoding used by the return value of
64-
* {@link encode}. One of 'buffer', 'view', 'utf8'. Defaults to 'buffer'
65-
* if the {@link buffer} and {@link code} options are also undefined.
66-
*/
67-
format?: 'buffer' | 'view' | 'utf8' | undefined
68-
6981
/**
7082
* Legacy `level-codec` option that means the same as `format: 'buffer'`
71-
* if true or `format: 'utf8'` if false. Used only when the {@link format} option
72-
* is undefined.
83+
* if true or `format: 'utf8'` if false.
7384
*/
7485
buffer?: boolean | undefined
7586

@@ -80,13 +91,68 @@ export interface EncodingOptions<TIn, TFormat, TOut> {
8091
type?: any
8192

8293
/**
83-
* For compatibility with `multiformats`. If a number, then the encoding is
84-
* assumed to have a {@link format} of 'view'. Used only when the {@link format}
85-
* and {@link buffer} options are undefined.
94+
* To detect `multiformats`. If a number, then the encoding is
95+
* assumed to have a {@link format} of 'view'.
8696
* @see https://github.com/multiformats/js-multiformats/blob/master/src/codecs/interface.ts
8797
*/
8898
code?: any
89-
90-
createViewTranscoder?: (() => ViewFormat<TIn, TOut>) | undefined
91-
createBufferTranscoder?: (() => BufferFormat<TIn, TOut>) | undefined
9299
}
100+
101+
/**
102+
* Names of built-in encodings.
103+
*/
104+
export type KnownEncodingName = 'utf8' | 'buffer' | 'view' | 'json' | 'hex' | 'base64'
105+
106+
/**
107+
* One of the supported encoding interfaces.
108+
*/
109+
export type MixedEncoding<TIn, TFormat, TOut> =
110+
IEncoding<TIn, TFormat, TOut> |
111+
IExternalEncoding<TIn, TFormat, TOut>
112+
113+
/**
114+
* Type utility to cast a built-in encoding identified by its name to an {@link Encoding}.
115+
*/
116+
export type KnownEncoding<N extends KnownEncodingName, TFormat>
117+
= Encoding<KnownEncodingInput<N>, TFormat, KnownEncodingOutput<N>>
118+
119+
/**
120+
* Type utility to get the input type of a built-in encoding identified by its name.
121+
*/
122+
export type KnownEncodingInput<N extends KnownEncodingName>
123+
= N extends 'utf8' ? string | Buffer | Uint8Array
124+
: N extends 'buffer' ? Buffer | Uint8Array | string
125+
: N extends 'view' ? Uint8Array | string
126+
: N extends 'json' ? any
127+
: N extends 'hex' ? Buffer | string
128+
: N extends 'base64' ? Buffer | string
129+
: never
130+
131+
/**
132+
* Type utility to get the output type of a built-in encoding identified by its name.
133+
*/
134+
export type KnownEncodingOutput<N extends KnownEncodingName>
135+
= N extends 'utf8' ? string
136+
: N extends 'buffer' ? Buffer
137+
: N extends 'view' ? Uint8Array
138+
: N extends 'json' ? any
139+
: N extends 'hex' ? string
140+
: N extends 'base64' ? string
141+
: never
142+
143+
/**
144+
* Type utility to use a {@link MixedEncoding} with an untyped format.
145+
*/
146+
export type PartialEncoding<TIn, TOut = TIn> = MixedEncoding<TIn, any, TOut>
147+
148+
/**
149+
* Type utility to use a {@link MixedEncoding} with an untyped format and output.
150+
* For when only the encoding side is needed.
151+
*/
152+
export type PartialEncoder<TIn> = MixedEncoding<TIn, any, any>
153+
154+
/**
155+
* Type utility to use a {@link MixedEncoding} with an untyped input and format.
156+
* For when only the decoding side is needed.
157+
*/
158+
export type PartialDecoder<TOut> = MixedEncoding<any, any, TOut>

0 commit comments

Comments
 (0)