diff --git a/README.md b/README.md
index a309993..fb8aabe 100644
--- a/README.md
+++ b/README.md
@@ -79,7 +79,7 @@ The **options** accepted are:
- `id`string: The id of the plan, it cannot contain whitespaces.
- `period`string: The time window which the limit applies. It accepts [ms](https://www.npmjs.com/package/ms) syntax.
- `limit`number: The target maximum number of requests that can be made in a given time period.
-- `metadata`object: A flat object containing additional information.
+- `metadata`object: A flat object containing additional information. Pass `null` or `''` to remove all the metadata fields.
Any other field provided will be omitted.
@@ -142,7 +142,7 @@ The **options** accepted are:
- `value`string: The value of the key, being a base58 16 length key generated by default.
- `enabled`string: It determines if the key is active, being `true` by default.
-- `metadata`object: A flat object containing additional information.
+- `metadata`object: A flat object containing additional information. Pass `null` or `''` to remove all the metadata fields.
Any other field provided will be omitted.
diff --git a/src/assert.js b/src/assert.js
new file mode 100644
index 0000000..c28a5d3
--- /dev/null
+++ b/src/assert.js
@@ -0,0 +1,10 @@
+const { errors } = require('./error')
+
+module.exports = (condition, code, args = () => []) => {
+ return (
+ condition ||
+ (() => {
+ throw errors[code](args)
+ })()
+ )
+}
diff --git a/src/error.js b/src/error.js
index 2a74dd0..5115b92 100644
--- a/src/error.js
+++ b/src/error.js
@@ -1,7 +1,5 @@
'use strict'
-const { isPlainObject } = require('./util')
-
class OpenKeyError extends Error {
constructor (props) {
super()
@@ -26,24 +24,4 @@ const errors = [
return acc
}, {})
-const assert = (condition, code, args = () => []) => {
- return (
- condition ||
- (() => {
- throw errors[code](args)
- })()
- )
-}
-
-const assertMetadata = metadata => {
- if (metadata) {
- assert(isPlainObject(metadata), 'ERR_METADATA_NOT_FLAT_OBJECT')
- Object.keys(metadata).forEach(key => {
- assert(!isPlainObject(metadata[key]), 'ERR_METADATA_INVALID', () => [key])
- if (metadata[key] === undefined) delete metadata[key]
- })
- return Object.keys(metadata).length ? metadata : false
- }
-}
-
-module.exports = { errors, assert, assertMetadata }
+module.exports = { errors }
diff --git a/src/keys.js b/src/keys.js
index aa9f82f..c5c0cdf 100644
--- a/src/keys.js
+++ b/src/keys.js
@@ -1,7 +1,8 @@
'use strict'
const { pick, uid } = require('./util')
-const { assert, assertMetadata } = require('./error')
+const metadata = require('./metadata')
+const assert = require('./assert')
module.exports = ({ serialize, deserialize, plans, redis, prefix } = {}) => {
/**
@@ -17,8 +18,8 @@ module.exports = ({ serialize, deserialize, plans, redis, prefix } = {}) => {
*/
const create = async (opts = {}) => {
const key = { enabled: opts.enabled ?? true }
- const metadata = assertMetadata(opts.metadata)
if (metadata) key.metadata = metadata
+ metadata(key, opts)
key.createdAt = key.updatedAt = Date.now()
const value = opts.value ?? (await uid({ redis, size: 16 }))
if (opts.plan) {
@@ -70,10 +71,8 @@ module.exports = ({ serialize, deserialize, plans, redis, prefix } = {}) => {
* @returns {Object} The updated plan.
*/
const update = async (value, opts) => {
- const currentKey = await retrieve(value, { throwError: true })
- const metadata = Object.assign({}, currentKey.metadata, assertMetadata(opts.metadata))
- const key = Object.assign(currentKey, { updatedAt: Date.now() }, pick(opts, ['enabled', 'value', 'plan']))
- if (Object.keys(metadata).length) key.metadata = metadata
+ let key = await retrieve(value, { throwError: true })
+ key = Object.assign(metadata(key, opts), { updatedAt: Date.now() }, pick(opts, ['enabled', 'value', 'plan']))
if (key.plan) await plans.retrieve(key.plan, { throwError: true })
return (await redis.set(prefixKey(value), await serialize(key))) && key
}
diff --git a/src/metadata.js b/src/metadata.js
new file mode 100644
index 0000000..7669aa0
--- /dev/null
+++ b/src/metadata.js
@@ -0,0 +1,38 @@
+'use strict'
+
+const { isPlainObject } = require('./util')
+const assert = require('./assert')
+
+const assertMetadata = metadata => {
+ if (metadata) {
+ assert(isPlainObject(metadata), 'ERR_METADATA_NOT_FLAT_OBJECT')
+ Object.keys(metadata).forEach(key => {
+ assert(!isPlainObject(metadata[key]), 'ERR_METADATA_INVALID', () => [key])
+ if (metadata[key] === undefined) delete metadata[key]
+ })
+
+ return Object.keys(metadata).length ? metadata : false
+ }
+}
+
+const clean = metadata => {
+ for (const [key, value] of Object.entries(metadata)) {
+ if ([null, ''].includes(value)) delete metadata[key]
+ }
+ return metadata
+}
+
+const merge = (metadata, newMetadata) => {
+ if (newMetadata === null) return null
+ const mergedMetadata = Object.assign({}, metadata, assertMetadata(newMetadata))
+ return clean(mergedMetadata)
+}
+
+const mutate = (obj, opts) => {
+ const metadata = merge(obj.metadata, opts.metadata)
+ if (metadata === null || Object.keys(metadata).length === 0) delete obj.metadata
+ else obj.metadata = metadata
+ return obj
+}
+
+module.exports = mutate
diff --git a/src/plans.js b/src/plans.js
index acd1d85..35d509c 100644
--- a/src/plans.js
+++ b/src/plans.js
@@ -1,6 +1,7 @@
'use strict'
-const { assert, assertMetadata } = require('./error')
+const assert = require('./assert')
+const metadata = require('./metadata')
module.exports = ({ serialize, deserialize, redis, keys, prefix } = {}) => {
/**
@@ -24,8 +25,7 @@ module.exports = ({ serialize, deserialize, redis, keys, prefix } = {}) => {
'ERR_PLAN_INVALID_PERIOD'
)
}
- const metadata = assertMetadata(opts.metadata)
- if (metadata) plan.metadata = metadata
+ metadata(plan, opts)
plan.createdAt = plan.updatedAt = Date.now()
const isCreated = (await redis.set(prefixKey(opts.id), await serialize(plan), 'NX')) === 'OK'
assert(isCreated, 'ERR_PLAN_ALREADY_EXIST', () => [opts.id])
@@ -78,8 +78,7 @@ module.exports = ({ serialize, deserialize, redis, keys, prefix } = {}) => {
* @returns {Object} The updated plan.
*/
const update = async (id, opts) => {
- const plan = await retrieve(id, { throwError: true })
- const metadata = Object.assign({}, plan.metadata, assertMetadata(opts.metadata))
+ let plan = await retrieve(id, { throwError: true })
if (opts.limit) {
plan.limit = assert(typeof opts.limit === 'number' && opts.limit > 0 && opts.limit, 'ERR_PLAN_INVALID_LIMIT')
@@ -92,9 +91,8 @@ module.exports = ({ serialize, deserialize, redis, keys, prefix } = {}) => {
)
}
- plan.updatedAt = Date.now()
+ plan = Object.assign(metadata(plan, opts), { updatedAt: Date.now() })
- if (Object.keys(metadata).length) plan.metadata = metadata
return (await redis.set(prefixKey(id), await serialize(plan))) && plan
}
diff --git a/test/keys.js b/test/keys.js
index 5c4e6d2..6cfa0f9 100644
--- a/test/keys.js
+++ b/test/keys.js
@@ -109,7 +109,7 @@ test('.update # error if plan does not exist', async t => {
t.is(error.code, 'ERR_PLAN_NOT_EXIST')
})
-test('.update # add a plan', async t => {
+test('.update # plan', async t => {
const plan = await openkey.plans.create({
id: randomUUID(),
limit: 3,
@@ -120,7 +120,7 @@ test('.update # add a plan', async t => {
t.is(key.plan, plan.id)
})
-test('.update # add metadata', async t => {
+test('.update # metadata', async t => {
{
const { value } = await openkey.keys.create()
const key = await openkey.keys.update(value, { metadata: { cc: 'hello@microlink.io' } })
@@ -134,6 +134,19 @@ test('.update # add metadata', async t => {
t.is(key.metadata.cc, 'hello@microlink.io')
t.is(key.metadata.version, 2)
}
+ {
+ const { value } = await openkey.keys.create()
+ const metadata = { tier: 'free', null: 'null', undefined: 'undefined', empty: '' }
+ let key = await openkey.keys.update(value, { metadata })
+ t.is(key.metadata.null, 'null')
+ t.is(key.metadata.undefined, 'undefined')
+ t.is(key.metadata.empty, undefined)
+ key = await openkey.keys.update(value, { metadata: { ...metadata, null: null, undefined: '' } })
+ t.is(key.metadata.null, undefined)
+ t.is(key.metadata.undefined, undefined)
+ key = await openkey.keys.update(value, { metadata: null })
+ t.is(key.metadata, undefined)
+ }
})
test('.update # error is metadata is not a flat object', async t => {
diff --git a/test/plans.js b/test/plans.js
index fe9fdf9..b7386aa 100644
--- a/test/plans.js
+++ b/test/plans.js
@@ -328,6 +328,24 @@ test('.update # metadata', async t => {
t.is(plan.metadata.tier, 'free')
t.is(plan.metadata.version, 2)
}
+ {
+ const { id } = await openkey.plans.create({
+ id: randomUUID(),
+ limit: 1,
+ period: '1s'
+ })
+
+ const metadata = { tier: 'free', null: 'null', undefined: 'undefined', empty: '' }
+ let plan = await openkey.plans.update(id, { metadata })
+ t.is(plan.metadata.null, 'null')
+ t.is(plan.metadata.undefined, 'undefined')
+ t.is(plan.metadata.empty, undefined)
+ plan = await openkey.plans.update(id, { metadata: { ...metadata, null: null, undefined: '' } })
+ t.is(plan.metadata.null, undefined)
+ t.is(plan.metadata.undefined, undefined)
+ plan = await openkey.plans.update(id, { metadata: null })
+ t.is(plan.metadata, undefined)
+ }
})
test('.update # error is metadata is not a flat object', async t => {