Skip to main content

Record And NumericKeyRecord

This page discusses how to use NumericRecord and Record.

Type Checks

info
  1. Record is Typescript native utility, we don't need to import it.

  2. NumericKeyRecord<T> is Record<${number}, T> under the hood.

  3. With Record<string, T>, the key must be non-numeric.

  4. With NumericKeyRecord<T>, the key must be numeric string with one exception: it can be number if used as object key because JS object coerce number key into string key.

  5. With NumericKeyRecord<T>, write operations accept T[] as values.

import {
MetaTypeCreator,
createRef,
getDatabase,
push,
set,
update,
NumericKeyRecord,
} from 'firesagejs'

type Example = MetaTypeCreator<{
a: NumericKeyRecord<{ c: number }>
b: Record<string, { c: number }>
}>

const exampleRef = createRef<Example>(getDatabase())

set(exampleRef('a'), { 123: { c: 999 } }) // ok, key is numeric
set(exampleRef('a'), [{ c: 999 }, { c: 111 }]) // ok, accept array, store in db as { 0:{ c: 999 }, 1:{ c: 111 } }
set(exampleRef('b'), { abc: { c: 999 } }) // ok, key is non-numeric

update(exampleRef('a'), ['123'], [{ c: 999 }]) // ok, key is numeric
update(exampleRef('b'), ['a1b2c3'], [{ c: 999 }]) // ok, key is non-numeric
update(
exampleRef(),
['a', 'b'],
[[{ c: 999 }, { c: 111 }], { abc: { c: 999 }, efg: { c: 111 } }]
) // ok, accept array, store in db as { a: { 0: { c: 999 }, 1: { c: 111 } }, b: { abc: { c: 999 }, efg: { c: 111 } }

set(exampleRef('a'), { a1b2c3: { c: 999 } }) // not ok, expect numeric key
set(exampleRef('b'), { 123: { c: 999 } }) // not ok, expect non-numeric key

update(exampleRef('a'), ['abc'], [{ c: 999 }]) // not ok, expect numeric key
update(exampleRef('b'), ['123'], [{ c: 999 }]) // not ok, expect non-numeric key

push(exampleRef('a'), { 123: { c: 999 } }) // not ok, cannot push NumericKeyRecord<T>
push(exampleRef('b'), { abc: { c: 999 } }) // not ok, cannot push Record<string, T>

update(exampleRef('a'), [123], [{ c: 999 }]) // not ok, is number but expect numeric string

// Record<`${number}`, T> and Record<number, T> is invalid type.
// use NumericKeyRecord<T> instead
type Example2 = MetaTypeCreator<{
a: Record<`${number}`, { c: number }>
b: Record<number, { c: number }>
}>
how to use `NumericRecord` and `Record

Understanding Error Messages

expect numeric key

expect non-numeric key

note

Due to complexity, the error suggestion for incorrect path check and numeric key check is generic, will give this deeper thoughts in the futrue.

expect numeric key

expect non-numeric key

cannot push NumericKeyRecord<T>

cannot push Record<string, T>

note

Due to complexity, the error suggestion for incorrect path check and numeric key check is generic, will give this deeper thoughts in the futrue.

received number but expect numeric string

Never Read As Array

According to this blog, we can:

  1. Write data as array
store data as array

  1. Possibly Read data as array
possibly read data as array

FireSageJS keeps the write data as array functionality, because it is convenient and has no drawbacks.

This is not true regarding possibly read data as array. Althought the Firebase team thinks this is a good idea, it introduces extra complexity: developers need to check the data type on runtime to see if it is an array or an object.

There are 2 ways to solve this:

  1. If it is an object, convert it to an array, else do nothing.

  2. If it is an array, convert it to an object, else do nothing. <-- FireSageJS implements this.

note

FireSageJS implements the 2nd approach and stays away from the 1st approach because converting an object to an array results in length value that is not meaningful.

For example, if we read {2: 'c', 4: 'e'} and convert it to an array would end up with [,,'c',,'e'] and the length is 5, giving us the impression that we have 5 elements.

But in fact, there are only 2 elements that really count; the rest are just empty elements.

Meanwhile, converting an array to object is safer. Take { 0: 'a', 2: 'c', 4: 'e' }, it is read as ['a', undefined, 'c', undefined, 'e'] and FireSageJS converts it back to { 0: 'a', 2: 'c', 4: 'e' }.

You may think that we lost some data because we discarded undefined, but actually we didn't.

This is because undefined is not a valid RTDB data type. We can read something as undefined, but we are never able to write something as undefined.

Now we know that undefined is safe to discard, what about length? Object.keys({ 0: 'a', 2: 'c', 4: 'e' }).length returns 3 and we do have 3 meaningful data points.

FireSageJS never reads data as an array, you always get an object or primitive data type.