{"version":3,"file":"bitcask.mjs","names":["openFile"],"sources":["../../src/_experiments/bitcask.ts"],"sourcesContent":["// Bitcask-inspired key-value store (minimal, single-process, single-writer)\n// Copyright 2024\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { FileHandle, open as openFile } from 'fs/promises'\n\nconst ENTRY_HEADER_SIZE = 17 // 4 (key) + 4 (value) + 1 (tombstone) + 8 (timestamp)\n\ninterface KeyDirEntry {\n  fileId: number\n  offset: number\n  size: number\n  timestamp: bigint\n  tombstone: boolean\n}\n\nexport class Bitcask {\n  private dirname: string\n  private readWrite: boolean\n  private syncOnPut: boolean\n  private keydir: Map<string, KeyDirEntry> = new Map();\n  private activeFile: fs.WriteStream | null = null;\n  private activeFileId: number = 0;\n  private activeOffset: number = 0;\n  private openFiles: Map<number, FileHandle> = new Map();\n\n  constructor(dirname: string, opts: { readWrite?: boolean; syncOnPut?: boolean } = {}) {\n    this.dirname = dirname\n    this.readWrite = opts.readWrite !== false\n    this.syncOnPut = opts.syncOnPut === true\n    if (!fs.existsSync(dirname)) fs.mkdirSync(dirname, { recursive: true })\n    this.loadFiles()\n    if (this.readWrite) this.rollover()\n  }\n\n  private listDataFiles(): string[] {\n    return fs.readdirSync(this.dirname).filter(f => f.endsWith('.data')).sort()\n  }\n\n  private loadFiles() {\n    for (const fname of this.listDataFiles()) {\n      const fileId = parseInt(fname.split('.')[0], 10)\n      const filePath = path.join(this.dirname, fname)\n      const fd = fs.openSync(filePath, 'r')\n      let offset = 0\n      while (true) {\n        const header = Buffer.alloc(ENTRY_HEADER_SIZE)\n        const bytesRead = fs.readSync(fd, header, 0, ENTRY_HEADER_SIZE, offset)\n        if (bytesRead < ENTRY_HEADER_SIZE) break\n        const keySize = header.readUInt32BE(0)\n        const valueSize = header.readUInt32BE(4)\n        const tombstone = header.readUInt8(8) === 1\n        const timestamp = header.readBigUInt64BE(9)\n        const key = Buffer.alloc(keySize)\n        fs.readSync(fd, key, 0, keySize, offset + ENTRY_HEADER_SIZE)\n        // skip value\n        const entrySize = ENTRY_HEADER_SIZE + keySize + valueSize\n        this.keydir.set(key.toString('binary'), {\n          fileId,\n          offset,\n          size: entrySize,\n          timestamp,\n          tombstone,\n        })\n        offset += entrySize\n      }\n      fs.closeSync(fd)\n    }\n  }\n\n  private rollover() {\n    const fileIds = this.listDataFiles().map(f => parseInt(f.split('.')[0], 10))\n    const nextId = fileIds.length ? Math.max(...fileIds) + 1 : 1\n    const fname = `${nextId.toString().padStart(8, '0')}.data`\n    const filePath = path.join(this.dirname, fname)\n    this.activeFile = fs.createWriteStream(filePath, { flags: 'a' })\n    this.activeFileId = nextId\n    this.activeOffset = fs.existsSync(filePath) ? fs.statSync(filePath).size : 0\n  }\n\n  async put(key: string | Buffer, value: string | Buffer) {\n    if (!this.readWrite) throw new Error('Read-only mode')\n    const keyBuf = Buffer.isBuffer(key) ? key : Buffer.from(key)\n    const valueBuf = Buffer.isBuffer(value) ? value : Buffer.from(value)\n    const timestamp = BigInt(Date.now())\n    const tombstone = 0\n    const header = Buffer.alloc(ENTRY_HEADER_SIZE)\n    header.writeUInt32BE(keyBuf.length, 0)\n    header.writeUInt32BE(valueBuf.length, 4)\n    header.writeUInt8(tombstone, 8)\n    header.writeBigUInt64BE(timestamp, 9)\n    const entry = Buffer.concat([header, keyBuf, valueBuf])\n    const offset = this.activeOffset\n    await new Promise<void>((resolve, reject) => {\n      this.activeFile!.write(entry, err => (err ? reject(err) : resolve()))\n    })\n    if (this.syncOnPut && this.activeFile) {\n      fs.fsyncSync((this.activeFile as any).fd)\n    }\n    this.keydir.set(keyBuf.toString('binary'), {\n      fileId: this.activeFileId,\n      offset,\n      size: entry.length,\n      timestamp,\n      tombstone: false,\n    })\n    this.activeOffset += entry.length\n    if (this.activeOffset > 32 * 1024 * 1024) {\n      this.activeFile!.close()\n      this.rollover()\n    }\n  }\n\n  async get(key: string | Buffer): Promise<Buffer | null> {\n    const keyBuf = Buffer.isBuffer(key) ? key : Buffer.from(key)\n    const meta = this.keydir.get(keyBuf.toString('binary'))\n    if (!meta || meta.tombstone) return null\n    let fh = this.openFiles.get(meta.fileId)\n    if (!fh) {\n      const fname = path.join(this.dirname, `${meta.fileId.toString().padStart(8, '0')}.data`)\n      fh = await openFile(fname, 'r')\n      this.openFiles.set(meta.fileId, fh)\n    }\n    const header = Buffer.alloc(ENTRY_HEADER_SIZE)\n    await fh.read(header, 0, ENTRY_HEADER_SIZE, meta.offset)\n    const keySize = header.readUInt32BE(0)\n    const valueSize = header.readUInt32BE(4)\n    const keyRead = Buffer.alloc(keySize)\n    await fh.read(keyRead, 0, keySize, meta.offset + ENTRY_HEADER_SIZE)\n    const value = Buffer.alloc(valueSize)\n    await fh.read(value, 0, valueSize, meta.offset + ENTRY_HEADER_SIZE + keySize)\n    return value\n  }\n\n  async delete(key: string | Buffer) {\n    if (!this.readWrite) throw new Error('Read-only mode')\n    const keyBuf = Buffer.isBuffer(key) ? key : Buffer.from(key)\n    const timestamp = BigInt(Date.now())\n    const tombstone = 1\n    const header = Buffer.alloc(ENTRY_HEADER_SIZE)\n    header.writeUInt32BE(keyBuf.length, 0)\n    header.writeUInt32BE(0, 4)\n    header.writeUInt8(tombstone, 8)\n    header.writeBigUInt64BE(timestamp, 9)\n    const entry = Buffer.concat([header, keyBuf])\n    const offset = this.activeOffset\n    await new Promise<void>((resolve, reject) => {\n      this.activeFile!.write(entry, err => (err ? reject(err) : resolve()))\n    })\n    if (this.syncOnPut && this.activeFile) {\n      fs.fsyncSync((this.activeFile as any).fd)\n    }\n    this.keydir.set(keyBuf.toString('binary'), {\n      fileId: this.activeFileId,\n      offset,\n      size: entry.length,\n      timestamp,\n      tombstone: true,\n    })\n    this.activeOffset += entry.length\n    if (this.activeOffset > 32 * 1024 * 1024) {\n      this.activeFile!.close()\n      this.rollover()\n    }\n  }\n\n  listKeys(): string[] {\n    return Array.from(this.keydir.entries()).filter(([_, v]) => !v.tombstone).map(([k, _]) => k)\n  }\n\n  async fold<T>(fn: (k: string, v: Buffer, acc: T) => T, acc0: T): Promise<T> {\n    for (const [k, meta] of this.keydir.entries()) {\n      if (!meta.tombstone) {\n        const v = await this.get(k)\n        acc0 = fn(k, v!, acc0)\n      }\n    }\n    return acc0\n  }\n\n  async merge() {\n    // Compact all but active file\n    const live: Map<string, KeyDirEntry> = new Map()\n    for (const [k, meta] of this.keydir.entries()) {\n      if (!meta.tombstone) live.set(k, meta)\n    }\n    const fileIds = this.listDataFiles().map(f => parseInt(f.split('.')[0], 10))\n    const nextId = fileIds.length ? Math.max(...fileIds) + 1 : 1\n    const fname = `${nextId.toString().padStart(8, '0')}.data`\n    const filePath = path.join(this.dirname, fname)\n    const f = fs.createWriteStream(filePath, { flags: 'a' })\n    for (const [k, meta] of live.entries()) {\n      const v = await this.get(k)\n      const keyBuf = Buffer.from(k, 'binary')\n      const header = Buffer.alloc(ENTRY_HEADER_SIZE)\n      header.writeUInt32BE(keyBuf.length, 0)\n      header.writeUInt32BE(v!.length, 4)\n      header.writeUInt8(0, 8)\n      header.writeBigUInt64BE(meta.timestamp, 9)\n      const entry = Buffer.concat([header, keyBuf, v!])\n      await new Promise<void>((resolve, reject) => {\n        f.write(entry, err => (err ? reject(err) : resolve()))\n      })\n    }\n    f.close()\n    for (const fid of fileIds) {\n      if (fid !== this.activeFileId && fid !== nextId) {\n        try {\n          fs.unlinkSync(path.join(this.dirname, `${fid.toString().padStart(8, '0')}.data`))\n        } catch { }\n      }\n    }\n    this.loadFiles()\n  }\n\n  sync() {\n    if (this.activeFile) fs.fsyncSync((this.activeFile as any).fd)\n  }\n\n  async close() {\n    if (this.activeFile) this.activeFile.end()\n    for (const fh of this.openFiles.values()) await fh.close()\n    this.openFiles.clear()\n  }\n}\n\n// API functions\nexport function open(dirname: string, opts?: { readWrite?: boolean; syncOnPut?: boolean }) {\n  return new Bitcask(dirname, opts)\n}\nexport async function get(handle: Bitcask, key: string | Buffer) {\n  const v = await handle.get(key)\n  return v === null ? 'not found' : v\n}\nexport async function put(handle: Bitcask, key: string | Buffer, value: string | Buffer) {\n  await handle.put(key, value)\n  return 'ok'\n}\nexport async function del(handle: Bitcask, key: string | Buffer) {\n  await handle.delete(key)\n  return 'ok'\n}\nexport function listKeys(handle: Bitcask) {\n  return handle.listKeys()\n}\nexport async function fold<T>(handle: Bitcask, fn: (k: string, v: Buffer, acc: T) => T, acc0: T) {\n  return await handle.fold(fn, acc0)\n}\nexport async function merge(dirname: string) {\n  const h = new Bitcask(dirname, { readWrite: true })\n  await h.merge()\n  h.close()\n  return 'ok'\n}\nexport function sync(handle: Bitcask) {\n  handle.sync()\n  return 'ok'\n}\nexport async function close(handle: Bitcask) {\n  await handle.close()\n  return 'ok'\n}\n"],"mappings":";;;;;AAMA,MAAM,oBAAoB;AAU1B,IAAa,UAAb,MAAqB;CACnB,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ,yBAAmC,IAAI,KAAK;CACpD,AAAQ,aAAoC;CAC5C,AAAQ,eAAuB;CAC/B,AAAQ,eAAuB;CAC/B,AAAQ,4BAAqC,IAAI,KAAK;CAEtD,YAAY,SAAiB,OAAqD,EAAE,EAAE;AACpF,OAAK,UAAU;AACf,OAAK,YAAY,KAAK,cAAc;AACpC,OAAK,YAAY,KAAK,cAAc;AACpC,MAAI,CAAC,GAAG,WAAW,QAAQ,CAAE,IAAG,UAAU,SAAS,EAAE,WAAW,MAAM,CAAC;AACvE,OAAK,WAAW;AAChB,MAAI,KAAK,UAAW,MAAK,UAAU;;CAGrC,AAAQ,gBAA0B;AAChC,SAAO,GAAG,YAAY,KAAK,QAAQ,CAAC,QAAO,MAAK,EAAE,SAAS,QAAQ,CAAC,CAAC,MAAM;;CAG7E,AAAQ,YAAY;AAClB,OAAK,MAAM,SAAS,KAAK,eAAe,EAAE;GACxC,MAAM,SAAS,SAAS,MAAM,MAAM,IAAI,CAAC,IAAI,GAAG;GAChD,MAAM,WAAW,KAAK,KAAK,KAAK,SAAS,MAAM;GAC/C,MAAM,KAAK,GAAG,SAAS,UAAU,IAAI;GACrC,IAAI,SAAS;AACb,UAAO,MAAM;IACX,MAAM,SAAS,OAAO,MAAM,kBAAkB;AAE9C,QADkB,GAAG,SAAS,IAAI,QAAQ,GAAG,mBAAmB,OAAO,GACvD,kBAAmB;IACnC,MAAM,UAAU,OAAO,aAAa,EAAE;IACtC,MAAM,YAAY,OAAO,aAAa,EAAE;IACxC,MAAM,YAAY,OAAO,UAAU,EAAE,KAAK;IAC1C,MAAM,YAAY,OAAO,gBAAgB,EAAE;IAC3C,MAAM,MAAM,OAAO,MAAM,QAAQ;AACjC,OAAG,SAAS,IAAI,KAAK,GAAG,SAAS,SAAS,kBAAkB;IAE5D,MAAM,YAAY,oBAAoB,UAAU;AAChD,SAAK,OAAO,IAAI,IAAI,SAAS,SAAS,EAAE;KACtC;KACA;KACA,MAAM;KACN;KACA;KACD,CAAC;AACF,cAAU;;AAEZ,MAAG,UAAU,GAAG;;;CAIpB,AAAQ,WAAW;EACjB,MAAM,UAAU,KAAK,eAAe,CAAC,KAAI,MAAK,SAAS,EAAE,MAAM,IAAI,CAAC,IAAI,GAAG,CAAC;EAC5E,MAAM,SAAS,QAAQ,SAAS,KAAK,IAAI,GAAG,QAAQ,GAAG,IAAI;EAC3D,MAAM,QAAQ,GAAG,OAAO,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC;EACpD,MAAM,WAAW,KAAK,KAAK,KAAK,SAAS,MAAM;AAC/C,OAAK,aAAa,GAAG,kBAAkB,UAAU,EAAE,OAAO,KAAK,CAAC;AAChE,OAAK,eAAe;AACpB,OAAK,eAAe,GAAG,WAAW,SAAS,GAAG,GAAG,SAAS,SAAS,CAAC,OAAO;;CAG7E,MAAM,IAAI,KAAsB,OAAwB;AACtD,MAAI,CAAC,KAAK,UAAW,OAAM,IAAI,MAAM,iBAAiB;EACtD,MAAM,SAAS,OAAO,SAAS,IAAI,GAAG,MAAM,OAAO,KAAK,IAAI;EAC5D,MAAM,WAAW,OAAO,SAAS,MAAM,GAAG,QAAQ,OAAO,KAAK,MAAM;EACpE,MAAM,YAAY,OAAO,KAAK,KAAK,CAAC;EACpC,MAAM,YAAY;EAClB,MAAM,SAAS,OAAO,MAAM,kBAAkB;AAC9C,SAAO,cAAc,OAAO,QAAQ,EAAE;AACtC,SAAO,cAAc,SAAS,QAAQ,EAAE;AACxC,SAAO,WAAW,WAAW,EAAE;AAC/B,SAAO,iBAAiB,WAAW,EAAE;EACrC,MAAM,QAAQ,OAAO,OAAO;GAAC;GAAQ;GAAQ;GAAS,CAAC;EACvD,MAAM,SAAS,KAAK;AACpB,QAAM,IAAI,SAAe,SAAS,WAAW;AAC3C,QAAK,WAAY,MAAM,QAAO,QAAQ,MAAM,OAAO,IAAI,GAAG,SAAS,CAAE;IACrE;AACF,MAAI,KAAK,aAAa,KAAK,WACzB,IAAG,UAAW,KAAK,WAAmB,GAAG;AAE3C,OAAK,OAAO,IAAI,OAAO,SAAS,SAAS,EAAE;GACzC,QAAQ,KAAK;GACb;GACA,MAAM,MAAM;GACZ;GACA,WAAW;GACZ,CAAC;AACF,OAAK,gBAAgB,MAAM;AAC3B,MAAI,KAAK,eAAe,KAAK,OAAO,MAAM;AACxC,QAAK,WAAY,OAAO;AACxB,QAAK,UAAU;;;CAInB,MAAM,IAAI,KAA8C;EACtD,MAAM,SAAS,OAAO,SAAS,IAAI,GAAG,MAAM,OAAO,KAAK,IAAI;EAC5D,MAAM,OAAO,KAAK,OAAO,IAAI,OAAO,SAAS,SAAS,CAAC;AACvD,MAAI,CAAC,QAAQ,KAAK,UAAW,QAAO;EACpC,IAAI,KAAK,KAAK,UAAU,IAAI,KAAK,OAAO;AACxC,MAAI,CAAC,IAAI;AAEP,QAAK,MAAMA,OADG,KAAK,KAAK,KAAK,SAAS,GAAG,KAAK,OAAO,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,EAC7D,IAAI;AAC/B,QAAK,UAAU,IAAI,KAAK,QAAQ,GAAG;;EAErC,MAAM,SAAS,OAAO,MAAM,kBAAkB;AAC9C,QAAM,GAAG,KAAK,QAAQ,GAAG,mBAAmB,KAAK,OAAO;EACxD,MAAM,UAAU,OAAO,aAAa,EAAE;EACtC,MAAM,YAAY,OAAO,aAAa,EAAE;EACxC,MAAM,UAAU,OAAO,MAAM,QAAQ;AACrC,QAAM,GAAG,KAAK,SAAS,GAAG,SAAS,KAAK,SAAS,kBAAkB;EACnE,MAAM,QAAQ,OAAO,MAAM,UAAU;AACrC,QAAM,GAAG,KAAK,OAAO,GAAG,WAAW,KAAK,SAAS,oBAAoB,QAAQ;AAC7E,SAAO;;CAGT,MAAM,OAAO,KAAsB;AACjC,MAAI,CAAC,KAAK,UAAW,OAAM,IAAI,MAAM,iBAAiB;EACtD,MAAM,SAAS,OAAO,SAAS,IAAI,GAAG,MAAM,OAAO,KAAK,IAAI;EAC5D,MAAM,YAAY,OAAO,KAAK,KAAK,CAAC;EACpC,MAAM,YAAY;EAClB,MAAM,SAAS,OAAO,MAAM,kBAAkB;AAC9C,SAAO,cAAc,OAAO,QAAQ,EAAE;AACtC,SAAO,cAAc,GAAG,EAAE;AAC1B,SAAO,WAAW,WAAW,EAAE;AAC/B,SAAO,iBAAiB,WAAW,EAAE;EACrC,MAAM,QAAQ,OAAO,OAAO,CAAC,QAAQ,OAAO,CAAC;EAC7C,MAAM,SAAS,KAAK;AACpB,QAAM,IAAI,SAAe,SAAS,WAAW;AAC3C,QAAK,WAAY,MAAM,QAAO,QAAQ,MAAM,OAAO,IAAI,GAAG,SAAS,CAAE;IACrE;AACF,MAAI,KAAK,aAAa,KAAK,WACzB,IAAG,UAAW,KAAK,WAAmB,GAAG;AAE3C,OAAK,OAAO,IAAI,OAAO,SAAS,SAAS,EAAE;GACzC,QAAQ,KAAK;GACb;GACA,MAAM,MAAM;GACZ;GACA,WAAW;GACZ,CAAC;AACF,OAAK,gBAAgB,MAAM;AAC3B,MAAI,KAAK,eAAe,KAAK,OAAO,MAAM;AACxC,QAAK,WAAY,OAAO;AACxB,QAAK,UAAU;;;CAInB,WAAqB;AACnB,SAAO,MAAM,KAAK,KAAK,OAAO,SAAS,CAAC,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,GAAG,OAAO,EAAE;;CAG9F,MAAM,KAAQ,IAAyC,MAAqB;AAC1E,OAAK,MAAM,CAAC,GAAG,SAAS,KAAK,OAAO,SAAS,CAC3C,KAAI,CAAC,KAAK,UAER,QAAO,GAAG,GADA,MAAM,KAAK,IAAI,EAAE,EACV,KAAK;AAG1B,SAAO;;CAGT,MAAM,QAAQ;EAEZ,MAAM,uBAAiC,IAAI,KAAK;AAChD,OAAK,MAAM,CAAC,GAAG,SAAS,KAAK,OAAO,SAAS,CAC3C,KAAI,CAAC,KAAK,UAAW,MAAK,IAAI,GAAG,KAAK;EAExC,MAAM,UAAU,KAAK,eAAe,CAAC,KAAI,MAAK,SAAS,EAAE,MAAM,IAAI,CAAC,IAAI,GAAG,CAAC;EAC5E,MAAM,SAAS,QAAQ,SAAS,KAAK,IAAI,GAAG,QAAQ,GAAG,IAAI;EAC3D,MAAM,QAAQ,GAAG,OAAO,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC;EACpD,MAAM,WAAW,KAAK,KAAK,KAAK,SAAS,MAAM;EAC/C,MAAM,IAAI,GAAG,kBAAkB,UAAU,EAAE,OAAO,KAAK,CAAC;AACxD,OAAK,MAAM,CAAC,GAAG,SAAS,KAAK,SAAS,EAAE;GACtC,MAAM,IAAI,MAAM,KAAK,IAAI,EAAE;GAC3B,MAAM,SAAS,OAAO,KAAK,GAAG,SAAS;GACvC,MAAM,SAAS,OAAO,MAAM,kBAAkB;AAC9C,UAAO,cAAc,OAAO,QAAQ,EAAE;AACtC,UAAO,cAAc,EAAG,QAAQ,EAAE;AAClC,UAAO,WAAW,GAAG,EAAE;AACvB,UAAO,iBAAiB,KAAK,WAAW,EAAE;GAC1C,MAAM,QAAQ,OAAO,OAAO;IAAC;IAAQ;IAAQ;IAAG,CAAC;AACjD,SAAM,IAAI,SAAe,SAAS,WAAW;AAC3C,MAAE,MAAM,QAAO,QAAQ,MAAM,OAAO,IAAI,GAAG,SAAS,CAAE;KACtD;;AAEJ,IAAE,OAAO;AACT,OAAK,MAAM,OAAO,QAChB,KAAI,QAAQ,KAAK,gBAAgB,QAAQ,OACvC,KAAI;AACF,MAAG,WAAW,KAAK,KAAK,KAAK,SAAS,GAAG,IAAI,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC;UAC3E;AAGZ,OAAK,WAAW;;CAGlB,OAAO;AACL,MAAI,KAAK,WAAY,IAAG,UAAW,KAAK,WAAmB,GAAG;;CAGhE,MAAM,QAAQ;AACZ,MAAI,KAAK,WAAY,MAAK,WAAW,KAAK;AAC1C,OAAK,MAAM,MAAM,KAAK,UAAU,QAAQ,CAAE,OAAM,GAAG,OAAO;AAC1D,OAAK,UAAU,OAAO;;;AAK1B,SAAgB,KAAK,SAAiB,MAAqD;AACzF,QAAO,IAAI,QAAQ,SAAS,KAAK;;AAEnC,eAAsB,IAAI,QAAiB,KAAsB;CAC/D,MAAM,IAAI,MAAM,OAAO,IAAI,IAAI;AAC/B,QAAO,MAAM,OAAO,cAAc;;AAEpC,eAAsB,IAAI,QAAiB,KAAsB,OAAwB;AACvF,OAAM,OAAO,IAAI,KAAK,MAAM;AAC5B,QAAO;;AAET,eAAsB,IAAI,QAAiB,KAAsB;AAC/D,OAAM,OAAO,OAAO,IAAI;AACxB,QAAO;;AAET,SAAgB,SAAS,QAAiB;AACxC,QAAO,OAAO,UAAU;;AAE1B,eAAsB,KAAQ,QAAiB,IAAyC,MAAS;AAC/F,QAAO,MAAM,OAAO,KAAK,IAAI,KAAK;;AAEpC,eAAsB,MAAM,SAAiB;CAC3C,MAAM,IAAI,IAAI,QAAQ,SAAS,EAAE,WAAW,MAAM,CAAC;AACnD,OAAM,EAAE,OAAO;AACf,GAAE,OAAO;AACT,QAAO;;AAET,SAAgB,KAAK,QAAiB;AACpC,QAAO,MAAM;AACb,QAAO;;AAET,eAAsB,MAAM,QAAiB;AAC3C,OAAM,OAAO,OAAO;AACpB,QAAO"}