Skip to content

Commit

Permalink
chore: add omit utility
Browse files Browse the repository at this point in the history
This change should have no impact on functionality.

This replaces the following pattern:

```javascript
// eslint-disable-next-line no-unused-vars
const { foo, bar, ...rest } = obj
return rest
```

With a new `omit` utility, which I think is clearer:

```javascript
return omit(obj, ['foo', 'bar'])
```

I think this is a useful change on its own, but also makes an upcoming
change slightly easier because I wanted this function.
  • Loading branch information
EvanHahn committed Oct 17, 2024
1 parent a7cec42 commit 11dc951
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 41 deletions.
1 change: 0 additions & 1 deletion src/config-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
7 changes: 5 additions & 2 deletions src/core-ownership.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
Expand Down
28 changes: 28 additions & 0 deletions src/lib/omit.js
Original file line number Diff line number Diff line change
@@ -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<K>} keys
* @returns {Omit<T, K>}
* @example
* const obj = { foo: 1, bar: 2, baz: 3 }
* omit(obj, ['foo', 'bar'])
* // => { baz: 3 }
*/
export function omit(obj, keys) {
/** @type {Partial<T>} */ const result = {}

/** @type {Set<unknown>} */ 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<T, K>} */ (result)
}
15 changes: 5 additions & 10 deletions src/mapeo-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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 })
}

Expand Down Expand Up @@ -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
)
}

Expand Down
5 changes: 2 additions & 3 deletions src/mapeo-project.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -887,9 +888,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'])
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/translation-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' */

Expand Down Expand Up @@ -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)
Expand Down
25 changes: 11 additions & 14 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -95,20 +96,16 @@ export function deNullify(obj) {
* @returns {Omit<T, 'docId' | 'versionId' | 'originalVersionId' | 'links' | 'forks' | 'createdAt' | 'updatedAt' | 'deleted'>}
*/
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',
])
}

/**
Expand Down
5 changes: 2 additions & 3 deletions test/datastore.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<import('@comapeo/schema').Observation, 'versionId'>} */
const obs = {
Expand Down Expand Up @@ -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)
Expand Down
65 changes: 65 additions & 0 deletions test/lib/omit.js
Original file line number Diff line number Diff line change
@@ -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<string, unknown>} [result]
* @returns {Record<string, unknown>}
*/
const record = (result = {}) => result
9 changes: 3 additions & 6 deletions test/translation-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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)
Expand All @@ -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`
)
Expand Down

0 comments on commit 11dc951

Please sign in to comment.