Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add omit utility #909

Merged
merged 1 commit into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 */
achou11 marked this conversation as resolved.
Show resolved Hide resolved
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 */
achou11 marked this conversation as resolved.
Show resolved Hide resolved
// 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
achou11 marked this conversation as resolved.
Show resolved Hide resolved
)
}

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
achou11 marked this conversation as resolved.
Show resolved Hide resolved

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
Loading