ai / nanoid

A tiny (124 bytes), secure, URL-friendly, unique string ID generator for JavaScript
https://zelark.github.io/nano-id-cc/
MIT License
24.66k stars 794 forks source link

NanoId to Uint8Array #496

Closed steida closed 2 months ago

steida commented 2 months ago

Because there is no NanoId to Uint8Array conversion, I spent two hours making it (one hour of googling any existing code, nothing exists). Maybe it can help someone. I did it for https://github.com/evoluhq/evolu

const nanoIdAlphabet =
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";

export const encodeId = (id: Id): WrappedBuffer => {
  /**
   * NanoId has 21 chars, but we can compress it to 16 bytes because of the
   * limited alphabet.
   */
  const value = new Uint8Array(16);
  let bitBuffer = 0;
  let bitsInBuffer = 0;
  let byteIndex = 0;
  for (let i = 0; i < 21; i++) {
    const charValue = nanoIdAlphabet.indexOf(id[i]);
    bitBuffer = (bitBuffer << 6) | charValue;
    bitsInBuffer += 6;
    while (bitsInBuffer >= 8) {
      bitsInBuffer -= 8;
      value[byteIndex++] = (bitBuffer >> bitsInBuffer) & 0xff;
    }
  }
  if (bitsInBuffer > 0) {
    value[byteIndex] = (bitBuffer << (8 - bitsInBuffer)) & 0xff;
  }
  return new WrappedBuffer(value);
};

export const decodeId = (
  buffer: WrappedBuffer,
): Effect.Effect<Id, BinaryParseEndsPrematurelyError> =>
  pipe(
    buffer.shiftN(16 as NonNegativeInt),
    Effect.map((value) => {
      let bitBuffer = 0;
      let bitsInBuffer = 0;
      let id = "";
      for (let i = 0; i < 16; i++) {
        bitBuffer = (bitBuffer << 8) | value[i];
        bitsInBuffer += 8;
        while (bitsInBuffer >= 6) {
          bitsInBuffer -= 6;
          const charValue = (bitBuffer >> bitsInBuffer) & 0x3f;
          id += nanoIdAlphabet[charValue];
        }
      }
      return id as Id;
    }),
  );
ai commented 2 months ago

Looks beautiful. I like it.

Have no idea where to put in docs. I will think about it later.

steida commented 2 months ago

@ai It's copy pasted from Evolu code so it uses WrappedBuffer, brands, and Effect. I think it should be easy to remove.