diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6cb9eee32..d890f2915 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,7 +37,7 @@ jobs: - uses: nearform-actions/optic-release-automation-action@v4 with: commit-message: 'Release {version}' - sync-semver-tags: true + sync-semver-tags: false access: 'public' # This prefix is added before the prerelease number, e.g. `v3.0.0-alpha.0` # prerelease-prefix: 'alpha' diff --git a/docs/api/md/-internal-/README.md b/docs/api/md/-internal-/README.md index 393cb87ce..78149f5ad 100644 --- a/docs/api/md/-internal-/README.md +++ b/docs/api/md/-internal-/README.md @@ -38,14 +38,12 @@ - [default](classes/default.md) - [default](classes/default.md) - [DenseIndex](classes/DenseIndex.md) -- [DriveLiveDownload](classes/DriveLiveDownload.md) - [Hyperblobs](classes/Hyperblobs.md) - [Hyperdrive](classes/Hyperdrive.md) - [IconApi](classes/IconApi.md) - [Index](classes/Index.md) - [IndexWriter](classes/IndexWriter.md) - [InviteApi](classes/InviteApi.md) -- [LiveDownload](classes/LiveDownload.md) - [LocalDiscovery](classes/LocalDiscovery.md) - [LocalPeers](classes/LocalPeers.md) - [Logger](classes/Logger.md) @@ -65,6 +63,7 @@ ### Interfaces - [BitmapOpts](interfaces/BitmapOpts.md) +- [BlobDownloadEvents](interfaces/BlobDownloadEvents.md) - [BlobDownloadState](interfaces/BlobDownloadState.md) - [Channel](interfaces/Channel.md) - [CorestoreEvents](interfaces/CorestoreEvents.md) @@ -137,6 +136,7 @@ - [PublicPeerInfo](type-aliases/PublicPeerInfo.md) - [Range](type-aliases/Range.md) - [RawStream](type-aliases/RawStream.md) +- [Readable](type-aliases/Readable.md) - [ReplicationStream](type-aliases/ReplicationStream.md) - [RoleId](type-aliases/RoleId.md) - [SchemaName](type-aliases/SchemaName.md) diff --git a/docs/api/md/-internal-/classes/BlobStore.md b/docs/api/md/-internal-/classes/BlobStore.md index 0b3e92579..38b8b3681 100644 --- a/docs/api/md/-internal-/classes/BlobStore.md +++ b/docs/api/md/-internal-/classes/BlobStore.md @@ -56,7 +56,7 @@ Enable to return an object with a `block` property with number of bytes removed ### createEntryReadStream() -> **createEntryReadStream**(`driveId`, `entry`, `options`?): `Promise`\<[`BlobReadStream`](BlobReadStream.md)\> +> **createEntryReadStream**(`driveId`, `entry`, `options`?): `Promise`\<[`Readable`](../type-aliases/Readable.md)\> Optimization for creating the blobs read stream when you have previously read the entry from Hyperdrive using `drive.entry` @@ -79,13 +79,13 @@ Set to `true` to wait for a blob to download, otherwise will throw if blob is no #### Returns -`Promise`\<[`BlobReadStream`](BlobReadStream.md)\> +`Promise`\<[`Readable`](../type-aliases/Readable.md)\> *** ### createReadStream() -> **createReadStream**(`blobId`, `options`?): `Readable`\<`any`, `any`, `any`, `true`, `false`, `ReadableEvents`\<`any`\>\> +> **createReadStream**(`blobId`, `options`?): [`Readable`](../type-aliases/Readable.md) #### Parameters @@ -103,7 +103,7 @@ Set to `true` to wait for a blob to download, otherwise will throw if blob is no #### Returns -`Readable`\<`any`, `any`, `any`, `true`, `false`, `ReadableEvents`\<`any`\>\> +[`Readable`](../type-aliases/Readable.md) *** @@ -129,7 +129,7 @@ Metadata to store with the blob ### download() -> **download**(`filter`?, `options`?): [`LiveDownload`](LiveDownload.md) +> **download**(`filter`?, `options`?): `TypedEmitter`\<[`BlobDownloadEvents`](../interfaces/BlobDownloadEvents.md)\> Download blobs from all drives, optionally filtering particular blob types or blob variants. Download will be 'live' and will continue downloading new @@ -152,9 +152,7 @@ Optional AbortSignal to cancel in-progress download #### Returns -[`LiveDownload`](LiveDownload.md) - -EventEmitter with `.state` propery, emits `state` with new state when it updates +`TypedEmitter`\<[`BlobDownloadEvents`](../interfaces/BlobDownloadEvents.md)\> *** @@ -188,7 +186,7 @@ Set to `true` to wait for a blob to download, otherwise will throw if blob is no ### get() -> **get**(`blobId`, `opts`): `Promise`\<`Buffer`\> +> **get**(`blobId`, `opts`): `Promise`\<`Uint8Array`\> #### Parameters @@ -206,7 +204,7 @@ Set to `true` to wait for a blob to download, otherwise will throw if blob is no #### Returns -`Promise`\<`Buffer`\> +`Promise`\<`Uint8Array`\> *** diff --git a/docs/api/md/-internal-/classes/DriveLiveDownload.md b/docs/api/md/-internal-/classes/DriveLiveDownload.md deleted file mode 100644 index f48ec4397..000000000 --- a/docs/api/md/-internal-/classes/DriveLiveDownload.md +++ /dev/null @@ -1,51 +0,0 @@ -[**API**](../../README.md) • **Docs** - -*** - -[API](../../README.md) / [\](../README.md) / DriveLiveDownload - -# Class: DriveLiveDownload - -LiveDownload class - -## Extends - -- `TypedEmitter` - -## Constructors - -### new DriveLiveDownload() - -> **new DriveLiveDownload**(`drive`, `options`): [`DriveLiveDownload`](DriveLiveDownload.md) - -Like drive.download() but 'live', - -#### Parameters - -• **drive**: [`Hyperdrive`](Hyperdrive.md) - -• **options** = `{}` - -• **options.filter**: `undefined` \| [`BlobFilter`](../type-aliases/BlobFilter.md) - -Filter blobs of specific types and/or sizes to download - -• **options.signal**: `undefined` \| `AbortSignal` - -#### Returns - -[`DriveLiveDownload`](DriveLiveDownload.md) - -#### Overrides - -`TypedEmitter.constructor` - -## Accessors - -### state - -> `get` **state**(): [`BlobDownloadState`](../interfaces/BlobDownloadState.md) \| [`BlobDownloadStateError`](../type-aliases/BlobDownloadStateError.md) - -#### Returns - -[`BlobDownloadState`](../interfaces/BlobDownloadState.md) \| [`BlobDownloadStateError`](../type-aliases/BlobDownloadStateError.md) diff --git a/docs/api/md/-internal-/classes/LiveDownload.md b/docs/api/md/-internal-/classes/LiveDownload.md deleted file mode 100644 index b558e80d7..000000000 --- a/docs/api/md/-internal-/classes/LiveDownload.md +++ /dev/null @@ -1,53 +0,0 @@ -[**API**](../../README.md) • **Docs** - -*** - -[API](../../README.md) / [\](../README.md) / LiveDownload - -# Class: LiveDownload - -LiveDownload class - -## Extends - -- `TypedEmitter` - -## Constructors - -### new LiveDownload() - -> **new LiveDownload**(`drives`, `emitter`, `options`): [`LiveDownload`](LiveDownload.md) - -Like drive.download() but 'live', and for multiple drives - -#### Parameters - -• **drives**: `Iterable`\<[`Hyperdrive`](Hyperdrive.md), `any`, `any`\> - -• **emitter**: [`InternalDriveEmitter`](../type-aliases/InternalDriveEmitter.md) - -• **options** - -• **options.filter**: `undefined` \| [`BlobFilter`](../type-aliases/BlobFilter.md) - -Filter blobs of specific types and/or sizes to download - -• **options.signal**: `undefined` \| `AbortSignal` - -#### Returns - -[`LiveDownload`](LiveDownload.md) - -#### Overrides - -`TypedEmitter.constructor` - -## Accessors - -### state - -> `get` **state**(): [`BlobDownloadState`](../interfaces/BlobDownloadState.md) \| [`BlobDownloadStateError`](../type-aliases/BlobDownloadStateError.md) - -#### Returns - -[`BlobDownloadState`](../interfaces/BlobDownloadState.md) \| [`BlobDownloadStateError`](../type-aliases/BlobDownloadStateError.md) diff --git a/docs/api/md/-internal-/interfaces/BlobDownloadEvents.md b/docs/api/md/-internal-/interfaces/BlobDownloadEvents.md new file mode 100644 index 000000000..dab74ecd4 --- /dev/null +++ b/docs/api/md/-internal-/interfaces/BlobDownloadEvents.md @@ -0,0 +1,23 @@ +[**API**](../../README.md) • **Docs** + +*** + +[API](../../README.md) / [\](../README.md) / BlobDownloadEvents + +# Interface: BlobDownloadEvents + +## Properties + +### state() + +> **state**: (`state`) => `void` + +Emitted with the current download state whenever it changes (not emitted during initial 'checking' status) + +#### Parameters + +• **state**: [`BlobDownloadState`](BlobDownloadState.md) \| [`BlobDownloadStateError`](../type-aliases/BlobDownloadStateError.md) + +#### Returns + +`void` diff --git a/docs/api/md/-internal-/type-aliases/Readable.md b/docs/api/md/-internal-/type-aliases/Readable.md new file mode 100644 index 000000000..9831d8533 --- /dev/null +++ b/docs/api/md/-internal-/type-aliases/Readable.md @@ -0,0 +1,11 @@ +[**API**](../../README.md) • **Docs** + +*** + +[API](../../README.md) / [\](../README.md) / Readable + +# Type Alias: Readable + +> **Readable**\<\>: `NodeReadable` \| `StreamxReadable` + +## Type Parameters diff --git a/package-lock.json b/package-lock.json index aa51b33c2..eea05dea7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@comapeo/core", - "version": "2.0.0", + "version": "2.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@comapeo/core", - "version": "2.0.0", + "version": "2.0.1", "license": "MIT", "dependencies": { "@comapeo/fallback-smp": "^1.0.0", @@ -48,7 +48,7 @@ "sodium-universal": "^4.0.0", "start-stop-state-machine": "^1.2.0", "streamx": "^2.19.0", - "styled-map-package": "^1.1.0", + "styled-map-package": "^2.0.0", "sub-encoder": "^2.1.1", "throttle-debounce": "^5.0.0", "tiny-typed-emitter": "^2.1.0", @@ -8824,9 +8824,9 @@ } }, "node_modules/styled-map-package": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/styled-map-package/-/styled-map-package-1.1.0.tgz", - "integrity": "sha512-QzaB5nvbuZKteIlGAFVEsxyuXWJQ5bObFntgrk9CZdsEl19ErTexr3vThpvl/ABCWhWYUe8N8kpR6XevxmHlDQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/styled-map-package/-/styled-map-package-2.0.0.tgz", + "integrity": "sha512-CGjyypTc+SQ97Y2XopJ9yOuB8aQMqAMWSLyUBStnntuomXNzgMUhhS9GOafyBhUJqm01UONWi82tJhXhZ05wWw==", "dependencies": { "@fastify/static": "^7.0.4", "@mapbox/sphericalmercator": "^1.2.0", diff --git a/package.json b/package.json index ab68b68ab..e479e5c79 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@comapeo/core", - "version": "2.0.0", + "version": "2.0.1", "description": "Offline p2p mapping library", "main": "src/index.js", "types": "dist/index.d.ts", @@ -191,7 +191,7 @@ "sodium-universal": "^4.0.0", "start-stop-state-machine": "^1.2.0", "streamx": "^2.19.0", - "styled-map-package": "^1.1.0", + "styled-map-package": "^2.0.0", "sub-encoder": "^2.1.1", "throttle-debounce": "^5.0.0", "tiny-typed-emitter": "^2.1.0", diff --git a/src/config-import.js b/src/config-import.js index 5990dc055..fe8be63dc 100644 --- a/src/config-import.js +++ b/src/config-import.js @@ -516,7 +516,6 @@ function parseIcon(filename, buf) { if (!matches) { throw new Error(`Unexpected icon filename ${filename}`) } - /* eslint-disable no-unused-vars */ const [_, name, size, pixelDensityStr] = matches const pixelDensity = Number(pixelDensityStr) if (!(pixelDensity === 1 || pixelDensity === 2 || pixelDensity === 3)) { diff --git a/src/core-ownership.js b/src/core-ownership.js index 38a595d28..47bd65487 100644 --- a/src/core-ownership.js +++ b/src/core-ownership.js @@ -15,6 +15,7 @@ import { discoveryKey } from 'hypercore-crypto' import pDefer from 'p-defer' import { NAMESPACES } from './constants.js' import { TypedEmitter } from 'tiny-typed-emitter' +import { omit } from './lib/omit.js' /** * @import { * CoreOwnershipWithSignatures, @@ -167,8 +168,10 @@ export function mapAndValidateCoreOwnership(doc, { coreDiscoveryKey }) { if (!verifyCoreOwnership(doc)) { throw new Error('Invalid coreOwnership record: signatures are invalid') } - // eslint-disable-next-line no-unused-vars - const { identitySignature, coreSignatures, ...docWithoutSignatures } = doc + const docWithoutSignatures = omit(doc, [ + 'identitySignature', + 'coreSignatures', + ]) docWithoutSignatures.links = [] return docWithoutSignatures } diff --git a/src/fastify-plugins/maps.js b/src/fastify-plugins/maps.js index a78ff0f53..d3df66e66 100644 --- a/src/fastify-plugins/maps.js +++ b/src/fastify-plugins/maps.js @@ -1,7 +1,7 @@ import fs from 'node:fs/promises' import path from 'node:path' import { fetch } from 'undici' -import { Server as SMPServerPlugin } from 'styled-map-package' +import { ReaderWatch, Server as SMPServerPlugin } from 'styled-map-package' import { noop } from '../utils.js' import { NotFoundError, ENOENTError } from './utils.js' @@ -25,6 +25,10 @@ export async function plugin(fastify, opts) { if (opts.customMapPath) { const { customMapPath } = opts + const customMapReader = new ReaderWatch(customMapPath) + + fastify.addHook('onClose', () => customMapReader.close().catch(noop)) + fastify.get(`/${CUSTOM_MAP_PREFIX}/info`, async () => { const baseUrl = new URL(fastify.prefix, fastify.listeningOrigin) @@ -78,13 +82,17 @@ export async function plugin(fastify, opts) { fastify.register(SMPServerPlugin, { prefix: CUSTOM_MAP_PREFIX, - filepath: customMapPath, + reader: customMapReader, }) } + const fallbackMapReader = new ReaderWatch(opts.fallbackMapPath) + + fastify.addHook('onClose', () => fallbackMapReader.close().catch(noop)) + fastify.register(SMPServerPlugin, { prefix: FALLBACK_MAP_PREFIX, - filepath: opts.fallbackMapPath, + reader: fallbackMapReader, }) fastify.get('/style.json', async (_request, reply) => { diff --git a/src/lib/omit.js b/src/lib/omit.js new file mode 100644 index 000000000..d0d34caf9 --- /dev/null +++ b/src/lib/omit.js @@ -0,0 +1,28 @@ +/** + * Returns a new object with the own enumerable keys of `obj` that are not in `keys`. + * + * In other words, remove some keys from an object. + * + * @template {object} T + * @template {keyof T} K + * @param {T} obj + * @param {ReadonlyArray} keys + * @returns {Omit} + * @example + * const obj = { foo: 1, bar: 2, baz: 3 } + * omit(obj, ['foo', 'bar']) + * // => { baz: 3 } + */ +export function omit(obj, keys) { + /** @type {Partial} */ const result = {} + + /** @type {Set} */ const toOmit = new Set(keys) + + for (const key in obj) { + if (!Object.hasOwn(obj, key)) continue + if (toOmit.has(key)) continue + result[key] = obj[key] + } + + return /** @type {Omit} */ (result) +} diff --git a/src/mapeo-manager.js b/src/mapeo-manager.js index 63f0a9c08..f88f357e0 100644 --- a/src/mapeo-manager.js +++ b/src/mapeo-manager.js @@ -34,6 +34,7 @@ import { projectKeyToPublicId, } from './utils.js' import { openedNoiseSecretStream } from './lib/noise-secret-stream-helpers.js' +import { omit } from './lib/omit.js' import { RandomAccessFilePool } from './core-manager/random-access-file-pool.js' import BlobServerPlugin from './fastify-plugins/blobs.js' import IconServerPlugin from './fastify-plugins/icons.js' @@ -442,9 +443,10 @@ export class MapeoManager extends TypedEmitter { // 7. Load config, if relevant // TODO: see how to expose warnings to frontend - /* eslint-disable no-unused-vars */ + // eslint-disable-next-line no-unused-vars let warnings if (configPath) { + // eslint-disable-next-line no-unused-vars warnings = await project.importConfig({ configPath }) } @@ -917,15 +919,8 @@ export class MapeoManager extends TypedEmitter { * @returns {PublicPeerInfo[]} */ function omitPeerProtomux(peers) { - return peers.map( - ({ - // @ts-ignore - // eslint-disable-next-line no-unused-vars - protomux, - ...publicPeerInfo - }) => { - return publicPeerInfo - } + return peers.map((peer) => + 'protomux' in peer ? omit(peer, ['protomux']) : peer ) } diff --git a/src/mapeo-project.js b/src/mapeo-project.js index 6dfd83ce7..a820d2c1a 100644 --- a/src/mapeo-project.js +++ b/src/mapeo-project.js @@ -43,6 +43,7 @@ import { projectKeyToPublicId, valueOf, } from './utils.js' +import { omit } from './lib/omit.js' import { MemberApi } from './member-api.js' import { SyncApi, kHandleDiscoveryKey } from './sync/sync-api.js' import { Logger } from './logger.js' @@ -897,9 +898,7 @@ export class MapeoProject extends TypedEmitter { * @returns {EditableProjectSettings} */ function extractEditableProjectSettings(projectDoc) { - // eslint-disable-next-line no-unused-vars - const { schemaName, ...result } = valueOf(projectDoc) - return result + return omit(valueOf(projectDoc), ['schemaName']) } /** diff --git a/src/translation-api.js b/src/translation-api.js index f9359dcda..05d0a92eb 100644 --- a/src/translation-api.js +++ b/src/translation-api.js @@ -2,6 +2,7 @@ import { and, sql } from 'drizzle-orm' import { kCreateWithDocId, kSelect } from './datatype/index.js' import { hashObject } from './utils.js' import { NotFoundError } from './errors.js' +import { omit } from './lib/omit.js' /** @import { Translation, TranslationValue } from '@comapeo/schema' */ /** @import { SetOptional } from 'type-fest' */ @@ -47,8 +48,7 @@ export default class TranslationApi { * @param {TranslationValue} value */ async put(value) { - /* eslint-disable no-unused-vars */ - const { message, ...identifiers } = value + const identifiers = omit(value, ['message']) const docId = hashObject(identifiers) try { const doc = await this.#dataType.getByDocId(docId) diff --git a/src/utils.js b/src/utils.js index da310a1a2..66dbe4341 100644 --- a/src/utils.js +++ b/src/utils.js @@ -2,6 +2,7 @@ import sodium from 'sodium-universal' import { keyToPublicId } from '@mapeo/crypto' import { createHash } from 'node:crypto' import stableStringify from 'json-stable-stringify' +import { omit } from './lib/omit.js' const PROJECT_INVITE_ID_SALT = Buffer.from('mapeo project invite id', 'ascii') @@ -95,20 +96,16 @@ export function deNullify(obj) { * @returns {Omit} */ export function valueOf(doc) { - /* eslint-disable no-unused-vars */ - const { - docId, - versionId, - originalVersionId, - links, - forks, - createdAt, - updatedAt, - deleted, - ...rest - } = doc - /* eslint-enable no-unused-vars */ - return rest + return omit(doc, [ + 'docId', + 'versionId', + 'originalVersionId', + 'links', + 'forks', + 'createdAt', + 'updatedAt', + 'deleted', + ]) } /** diff --git a/test/datastore.js b/test/datastore.js index 57440d375..fc8299cb8 100644 --- a/test/datastore.js +++ b/test/datastore.js @@ -7,6 +7,7 @@ import { getVersionId } from '@comapeo/schema' import { once } from 'events' import RAM from 'random-access-memory' import { discoveryKey } from 'hypercore-crypto' +import { omit } from '../src/lib/omit.js' /** @type {Omit} */ const obs = { @@ -102,9 +103,7 @@ test('index events', async () => { storage: () => new RAM(), }) dataStore.indexer.on('index-state', (state) => { - // eslint-disable-next-line no-unused-vars - const { entriesPerSecond, ...rest } = state - indexStates.push(rest) + indexStates.push(omit(state, ['entriesPerSecond'])) }) const idlePromise = once(dataStore.indexer, 'idle') await dataStore.write(obs) diff --git a/test/lib/omit.js b/test/lib/omit.js new file mode 100644 index 000000000..89d84e68d --- /dev/null +++ b/test/lib/omit.js @@ -0,0 +1,65 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { omit } from '../../src/lib/omit.js' + +test('an empty list of keys to omit', () => { + assert.deepEqual(omit({}, []), {}) + assert.deepEqual(omit({ foo: 1 }, []), { foo: 1 }) +}) + +test('omitting non-existent properties', () => { + assert.deepEqual(omit(record(), ['foo', 'bar']), {}) + assert.deepEqual(omit(record({ foo: 1 }), ['bar', 'baz']), { foo: 1 }) +}) + +test('omitting properties', () => { + const obj = { foo: 1, bar: 2, baz: 3 } + assert.deepEqual(omit(obj, ['baz']), { foo: 1, bar: 2 }) + assert.deepEqual(omit(obj, ['bar', 'baz']), { foo: 1 }) +}) + +test('only includes "own" properties in the result', () => { + class Klass { + foo = 1 + bar = 2 + baz() { + return 3 + } + } + + const omitted = omit(new Klass(), ['bar']) + assert.deepEqual(omitted, { foo: 1 }, 'plain object is returned') + assert(!(omitted instanceof Klass), 'inheritance is lost after omitting') + assert(!('baz' in omitted), 'inherited properties are lost after omitting') + + const obj = new Klass() + obj.baz = () => 4 + assert.equal(omit(obj, [])?.baz(), 4, 'own properties can be kept') + assert(!('baz' in omit(obj, ['baz'])), 'own properties can be removed') +}) + +test('only includes enumerable properties', () => { + const obj = { foo: 1 } + Object.defineProperty(obj, 'bar', { enumerable: true, value: 2 }) + Object.defineProperty(obj, 'baz', { enumerable: false, value: 3 }) + + assert.deepEqual(omit(obj, ['foo']), { bar: 2 }) +}) + +test("doesn't modify the input", () => { + const obj = { foo: 1, bar: 2, baz: 3 } + omit(obj, []) + omit(obj, ['foo', 'bar']) + assert.deepEqual( + obj, + { foo: 1, bar: 2, baz: 3 }, + 'input should not be modified' + ) +}) + +/** + * Convenience helper to satisfy TypeScript. + * @param {Record} [result] + * @returns {Record} + */ +const record = (result = {}) => result diff --git a/test/translation-api.js b/test/translation-api.js index 8a8a8b412..26c54fa2b 100644 --- a/test/translation-api.js +++ b/test/translation-api.js @@ -13,6 +13,7 @@ import { createCoreManager } from './helpers/core-manager.js' import { IndexWriter } from '../src/index-writer/index.js' import RAM from 'random-access-memory' import { hashObject } from '../src/utils.js' +import { omit } from '../src/lib/omit.js' import { randomBytes } from 'node:crypto' test('translation api - put() and get()', async () => { @@ -43,8 +44,7 @@ test('translation api - put() and get()', async () => { 'the map we use to caching translations is empty before calling put' ) - /* eslint-disable no-unused-vars */ - const { message, ...identifiers } = doc + const identifiers = omit(doc, ['message']) const expectedDocId = hashObject(identifiers) const { docId } = await api.put(doc) api.index(doc) @@ -69,11 +69,8 @@ test('translation api - put() and get()', async () => { `we've effectively have fields in spanish` ) - /* eslint-disable no-unused-vars */ - const { schemaName, message: msg, ...docToGet } = doc - assert.equal( - (await api.get(docToGet)).length, + (await api.get(doc)).length, 1, `using the doc without schema name to get the translation works` )