{"version":3,"file":"patchPgProtocol.cjs","names":["createRequire","#fields","dirname","join"],"sources":["../../src/drivers/patchPgProtocol.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { dirname, join } from \"node:path\";\n\nconst runtimeRequire = createRequire(import.meta.url);\n\n/**\n * Patches pg-protocol at runtime so that DataRow messages are lazy *and* their bytes are\n * immutable, i.e. so `WireRowData` can retain raw row payloads by reference — no per-cell\n * strings, no per-row copies, no arena.\n *\n * We patch at runtime (instead of shipping a patched pg-protocol) so that installing joist-orm\n * requires no package-manager patch steps; see JS-ROW-STORE-DESIGN.md \"Distribution\". Two\n * prototype methods are replaced, porting brianc/node-postgres#3719 (which may or may not ever\n * land upstream — this patch is how we run its design in production regardless):\n *\n * - `handlePacket` (stable across pg-protocol 1.10-1.15) returns a {@link LazyDataRowMessage}\n *   for DataRow packets and delegates every other message type to the original.\n * - `parse` replaces the recycled-scratch buffer management: each socket chunk is parsed *in\n *   place*, and only the one message per chunk that straddles a read boundary is reassembled\n *   into a buffer of its own, which is handed to that message and never touched again. Every\n *   message therefore stays valid for as long as it is held (holding one pins the ~64 KiB chunk\n *   it came from) — versus stock pg-protocol, which copies whole chunks into a scratch buffer\n *   and compacts it in place over the bytes earlier messages point into.\n *\n * The patch verifies itself by round-tripping synthetic DataRows through a fresh `Parser` —\n * including a message that straddles chunks, retained across later reads that would clobber it\n * under the stock buffer management. On any mismatch (i.e. a future pg-protocol internals\n * rewrite), it un-patches and reports failure so the driver can fall back to classic rows.\n *\n * The lazy message's memoized `fields` getter decodes identically to the original for classic\n * consumers, i.e. pg's own `Result.parseRow(msg.fields)`.\n */\nexport function ensureLazyDataRows(): boolean {\n  patched ??= tryPatch() ? \"applied\" : \"failed\";\n  return patched === \"applied\";\n}\n\n/** A drop-in `DataRowMessage` that defers cell decoding; lazy consumers read `bytes`/`offset`. */\nexport class LazyDataRowMessage {\n  public readonly name = \"dataRow\";\n  public readonly fieldCount: number;\n  #fields: (string | null)[] | undefined = undefined;\n\n  constructor(\n    public readonly length: number,\n    /** The row's payload bytes; immutable under our `parse` patch, so safe to retain. */\n    public readonly bytes: Buffer,\n    public readonly offset: number,\n  ) {\n    this.fieldCount = bytes.readInt16BE(offset);\n  }\n\n  /** Lazily materializes the per-cell strings for classic consumers, i.e. pg's `Result.parseRow`. */\n  get fields(): (string | null)[] {\n    let fields = this.#fields;\n    if (fields === undefined) {\n      const { bytes, fieldCount } = this;\n      fields = new Array(fieldCount);\n      let pos = this.offset + 2;\n      for (let i = 0; i < fieldCount; i++) {\n        const len = bytes.readInt32BE(pos);\n        pos += 4;\n        if (len === -1) {\n          fields[i] = null;\n        } else {\n          fields[i] = bytes.toString(\"utf8\", pos, pos + len);\n          pos += len;\n        }\n      }\n      this.#fields = fields;\n    }\n    return fields;\n  }\n}\n\nlet patched: \"applied\" | \"failed\" | undefined;\n\n/** The 'D' message-type byte identifying a DataRow packet. */\nconst DATA_ROW_CODE = 68;\nconst CODE_LENGTH = 1;\nconst HEADER_LENGTH = 5;\n/** Cap on up-front allocation for a straddling message's claimed length, i.e. corrupt streams. */\nconst MAX_EAGER_MESSAGE_LENGTH = 1024 * 1024;\nconst emptyBuffer = Buffer.allocUnsafe(0);\n\n/** Resolves pg's own pg-protocol Parser class and swaps in the lazy/immutable DataRow handling. */\nfunction tryPatch(): boolean {\n  try {\n    const { Parser, version } = resolvePgProtocol();\n    // Only patch the protocol major we've verified `handlePacket`'s shape against (1.10-1.15);\n    // anything else fails closed (the driver then uses classic rows) until explicitly vetted\n    if (!version.startsWith(\"1.\")) return false;\n    const originalHandlePacket = Parser.prototype.handlePacket;\n    const originalParse = Parser.prototype.parse;\n    if (typeof originalHandlePacket !== \"function\" || typeof originalParse !== \"function\") return false;\n    Parser.prototype.handlePacket = function (offset: number, code: number, length: number, bytes: Buffer) {\n      if (code === DATA_ROW_CODE) return new LazyDataRowMessage(length, bytes, offset);\n      return originalHandlePacket.call(this, offset, code, length, bytes);\n    };\n    Parser.prototype.parse = patchedParse;\n    if (!verifyLazyDataRows(Parser) || !verifyImmutableBytes(Parser)) {\n      Parser.prototype.handlePacket = originalHandlePacket;\n      Parser.prototype.parse = originalParse;\n      return false;\n    }\n    return true;\n  } catch {\n    return false;\n  }\n}\n\n/**\n * The ported #3719 `parse`: chunks are parsed in place, and only the message that straddles a\n * read boundary is reassembled into its own buffer (see `completePartial`). Partial state lives\n * on the parser instance under `_joist`-prefixed keys, so the stock fields are simply unused.\n */\nfunction patchedParse(this: any, chunk: Buffer, callback: (msg: any) => void): void {\n  const chunkLength = chunk.byteLength;\n  // finish the message the previous chunk cut in half, if any, before parsing this one\n  let offset = this._joistPartialLength > 0 ? completePartial(this, chunk, callback) : 0;\n  while (offset + HEADER_LENGTH <= chunkLength) {\n    const code = chunk[offset];\n    const length = chunk.readUInt32BE(offset + CODE_LENGTH);\n    const fullMessageLength = CODE_LENGTH + length;\n    if (fullMessageLength + offset <= chunkLength) {\n      const message = this.handlePacket(offset + HEADER_LENGTH, code, length, chunk);\n      callback(message);\n      offset += fullMessageLength;\n    } else {\n      break;\n    }\n  }\n  if (offset < chunkLength) {\n    startPartial(this, chunk, offset);\n  }\n}\n\n/** Copies the trailing bytes of `chunk` that do not yet form a whole message into their own buffer. */\nfunction startPartial(parser: any, chunk: Buffer, offset: number): void {\n  const remaining = chunk.byteLength - offset;\n  parser._joistPartial = emptyBuffer;\n  parser._joistPartialLength = 0;\n  // the length is only known once the header is complete\n  parser._joistPartialTotal = remaining >= HEADER_LENGTH ? CODE_LENGTH + chunk.readUInt32BE(offset + CODE_LENGTH) : -1;\n  growPartial(parser, remaining);\n  chunk.copy(parser._joistPartial, 0, offset);\n  parser._joistPartialLength = remaining;\n}\n\n/**\n * Fills the straddling message from the head of `chunk` and emits it once whole, returning the\n * offset in `chunk` where parsing continues. The buffer is handed to the message and forgotten,\n * so nothing is ever parsed twice out of the same bytes and the message stays valid when held.\n */\nfunction completePartial(parser: any, chunk: Buffer, callback: (msg: any) => void): number {\n  let consumed = 0;\n  if (parser._joistPartialTotal === -1) {\n    // the header itself was split, so complete it before its length can be read\n    consumed = Math.min(HEADER_LENGTH - parser._joistPartialLength, chunk.byteLength);\n    chunk.copy(parser._joistPartial, parser._joistPartialLength, 0, consumed);\n    parser._joistPartialLength += consumed;\n    if (parser._joistPartialLength < HEADER_LENGTH) return consumed;\n    parser._joistPartialTotal = CODE_LENGTH + parser._joistPartial.readUInt32BE(CODE_LENGTH);\n  }\n  if (parser._joistPartialLength < parser._joistPartialTotal) {\n    const available = Math.min(parser._joistPartialTotal - parser._joistPartialLength, chunk.byteLength - consumed);\n    growPartial(parser, parser._joistPartialLength + available);\n    chunk.copy(parser._joistPartial, parser._joistPartialLength, consumed, consumed + available);\n    parser._joistPartialLength += available;\n    consumed += available;\n    if (parser._joistPartialLength < parser._joistPartialTotal) return consumed;\n  }\n  const partial = parser._joistPartial;\n  parser._joistPartial = emptyBuffer;\n  parser._joistPartialLength = 0;\n  parser._joistPartialTotal = -1;\n  const message = parser.handlePacket(HEADER_LENGTH, partial[0], partial.readUInt32BE(CODE_LENGTH), partial);\n  callback(message);\n  return consumed;\n}\n\n/** Sizes the partial buffer to hold at least `needed` bytes, i.e. the whole message when known. */\nfunction growPartial(parser: any, needed: number): void {\n  if (needed <= parser._joistPartial.byteLength) return;\n  const total = parser._joistPartialTotal;\n  // grow geometrically, jumping straight to the message's full length when known and modest\n  const whole = total === -1 ? HEADER_LENGTH : Math.min(total, MAX_EAGER_MESSAGE_LENGTH);\n  let capacity = Math.max(needed, whole, parser._joistPartial.byteLength * 2);\n  if (total !== -1 && capacity > total) capacity = total;\n  const grown = Buffer.allocUnsafe(capacity);\n  parser._joistPartial.copy(grown, 0, 0, parser._joistPartialLength);\n  parser._joistPartial = grown;\n}\n\n/** Loads the Parser class + version from the pg-protocol instance that `pg` itself resolves to. */\nfunction resolvePgProtocol(): { Parser: any; version: string } {\n  // Resolve pg-protocol relative to pg, so that we patch the copy pg's Connections actually\n  // use, even if the dependency tree has multiple pg-protocol installs\n  const pgPath = runtimeRequire.resolve(\"pg\");\n  const parserPath = runtimeRequire.resolve(\"pg-protocol/dist/parser.js\", { paths: [dirname(pgPath)] });\n  // An absolute-path require bypasses the package's exports map, so this is always readable\n  const { version } = runtimeRequire(join(dirname(parserPath), \"..\", \"package.json\"));\n  return { Parser: runtimeRequire(parserPath).Parser, version };\n}\n\n/** Round-trips a synthetic two-cell DataRow (utf8 + NULL) through a fresh patched Parser. */\nfunction verifyLazyDataRows(Parser: any): boolean {\n  const cell = Buffer.from(\"wörld\", \"utf8\");\n  const payload = Buffer.concat([\n    Buffer.from([0, 2]), // int16 fieldCount\n    int32(cell.length),\n    cell,\n    int32(-1), // NULL cell\n  ]);\n  const frame = Buffer.concat([Buffer.from(\"D\"), int32(payload.length + 4), payload]);\n  let ok = false;\n  new Parser().parse(frame, (msg: any) => {\n    ok =\n      msg.name === \"dataRow\" &&\n      msg.fieldCount === 2 &&\n      msg.bytes !== undefined &&\n      typeof msg.offset === \"number\" &&\n      msg.fields.length === 2 &&\n      msg.fields[0] === \"wörld\" &&\n      msg.fields[1] === null;\n  });\n  return ok;\n}\n\n/**\n * Verifies a straddling DataRow's bytes survive later parser reads.\n *\n * We retain a message that straddles two chunks, drive a third read shaped to trigger the stock\n * parser's move-to-front compaction over its bytes, and only then decode the retained message's\n * cell (the `fields` getter memoizes, so it must not be read earlier). Under our `parse` patch\n * the straddler owns its buffer, so the cell decodes intact.\n */\nfunction verifyImmutableBytes(Parser: any): boolean {\n  const cellB = \"b\".repeat(85);\n  const frameA = dataRowFrame(\"aaaa\"); // 15 bytes, fully inside chunk1\n  const frameB = dataRowFrame(cellB); // 96 bytes, straddles chunk1/chunk2\n  const frameC = dataRowFrame(\"c\".repeat(45)); // 56 bytes, straddles chunk2/chunk3\n  const chunk1 = Buffer.concat([frameA, frameB.subarray(0, 50)]);\n  const chunk2 = Buffer.concat([frameB.subarray(50), frameC.subarray(0, 30)]);\n  const chunk3 = Buffer.from(frameC.subarray(30));\n  const messages: any[] = [];\n  const parser = new Parser();\n  function collect(msg: any): void {\n    messages.push(msg);\n  }\n  parser.parse(chunk1, collect);\n  parser.parse(chunk2, collect);\n  parser.parse(chunk3, collect);\n  try {\n    return messages.length === 3 && messages[1].name === \"dataRow\" && messages[1].fields[0] === cellB;\n  } catch {\n    return false;\n  }\n}\n\n/** Builds a complete one-cell DataRow frame (code + int32 length + payload) for `cell`. */\nfunction dataRowFrame(cell: string): Buffer {\n  const bytes = Buffer.from(cell, \"utf8\");\n  const payload = Buffer.concat([Buffer.from([0, 1]), int32(bytes.length), bytes]);\n  return Buffer.concat([Buffer.from(\"D\"), int32(payload.length + 4), payload]);\n}\n\n/** Returns a 4-byte big-endian buffer for `value`. */\nfunction int32(value: number): Buffer {\n  const buffer = Buffer.alloc(4);\n  buffer.writeInt32BE(value);\n  return buffer;\n}\n"],"mappings":"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"}