@effect/sql-pg 4.0.0-rc.112 → 4.0.0-rc.113

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/AGENTS.md +24 -9
  2. package/CLAUDE.md +24 -9
  3. package/ai-docs/package.json +2 -2
  4. package/ai-docs/src/01_effect/01_basics/02_effect-fn.ts +18 -5
  5. package/ai-docs/src/01_effect/01_basics/index.md +5 -3
  6. package/ai-docs/src/01_effect/03_services/20_layer-composition.ts +1 -1
  7. package/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts +2 -2
  8. package/ai-docs/src/01_effect/05_resources/10_acquire-release.ts +2 -2
  9. package/ai-docs/src/03_stream/30_encoding.ts +5 -7
  10. package/ai-docs/src/08_observability/10_logging.ts +1 -1
  11. package/ai-docs/src/70_cli/10_basics.ts +7 -7
  12. package/ai-docs/src/71_ai/10_language-model.ts +2 -2
  13. package/ai-docs/src/71_ai/20_tools.ts +1 -1
  14. package/ai-docs/src/71_ai/30_chat.ts +1 -1
  15. package/dist/PgAuth.d.ts +24 -0
  16. package/dist/PgAuth.d.ts.map +1 -1
  17. package/dist/PgAuth.js +29 -2
  18. package/dist/PgAuth.js.map +1 -1
  19. package/dist/PgClient.d.ts +49 -82
  20. package/dist/PgClient.d.ts.map +1 -1
  21. package/dist/PgClient.js +58 -510
  22. package/dist/PgClient.js.map +1 -1
  23. package/dist/PgConnection.d.ts +177 -0
  24. package/dist/PgConnection.d.ts.map +1 -0
  25. package/dist/PgConnection.js +1786 -0
  26. package/dist/PgConnection.js.map +1 -0
  27. package/dist/PgPool.d.ts +107 -0
  28. package/dist/PgPool.d.ts.map +1 -0
  29. package/dist/PgPool.js +124 -0
  30. package/dist/PgPool.js.map +1 -0
  31. package/dist/PgProtocol.d.ts +63 -30
  32. package/dist/PgProtocol.d.ts.map +1 -1
  33. package/dist/PgProtocol.js +64 -22
  34. package/dist/PgProtocol.js.map +1 -1
  35. package/dist/PgTypes.d.ts +85 -17
  36. package/dist/PgTypes.d.ts.map +1 -1
  37. package/dist/PgTypes.js +166 -169
  38. package/dist/PgTypes.js.map +1 -1
  39. package/dist/index.d.ts +8 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +8 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/internal/connection.d.ts +2 -0
  44. package/dist/internal/connection.d.ts.map +1 -0
  45. package/dist/internal/connection.js +5 -0
  46. package/dist/internal/connection.js.map +1 -0
  47. package/dist/internal/sqlError.d.ts +10 -0
  48. package/dist/internal/sqlError.d.ts.map +1 -0
  49. package/dist/internal/sqlError.js +57 -0
  50. package/dist/internal/sqlError.js.map +1 -0
  51. package/package.json +6 -14
  52. package/src/PgAuth.ts +33 -2
  53. package/src/PgClient.ts +138 -681
  54. package/src/PgConnection.ts +2294 -0
  55. package/src/PgPool.ts +231 -0
  56. package/src/PgProtocol.ts +93 -50
  57. package/src/PgTypes.ts +254 -186
  58. package/src/index.ts +10 -0
  59. package/src/internal/connection.ts +26 -0
  60. package/src/internal/sqlError.ts +75 -0
package/src/PgTypes.ts CHANGED
@@ -18,6 +18,9 @@
18
18
  */
19
19
  import * as Data from "effect/Data"
20
20
  import * as Result from "effect/Result"
21
+ import * as IpInterface from "effect/unstable/net/IpInterface"
22
+ import * as IpNetwork from "effect/unstable/net/IpNetwork"
23
+ import * as NetAddress from "effect/unstable/net/NetAddress"
21
24
  import type * as PgProtocol from "./PgProtocol.ts"
22
25
  import type { ValueSink } from "./PgProtocol.ts"
23
26
 
@@ -355,6 +358,31 @@ const elementToArray = new Map<number, number>(
355
358
  Array.from(arrayToElement, ([array, element]) => [element, array])
356
359
  )
357
360
 
361
+ /**
362
+ * Options for registering a codec.
363
+ *
364
+ * @category models
365
+ * @since 4.0.0
366
+ */
367
+ export interface RegisterOptions {
368
+ /**
369
+ * Registers a one-dimensional array codec at this OID and enables array type
370
+ * inference for the scalar codec.
371
+ */
372
+ readonly arrayOid?: number | undefined
373
+ }
374
+
375
+ /**
376
+ * A client-specific set of PostgreSQL binary codecs. Each registry starts with
377
+ * the built-in codecs and does not affect the module-level registry.
378
+ *
379
+ * @category models
380
+ * @since 4.0.0
381
+ */
382
+ export interface Registry {
383
+ readonly register: <A>(oid: number, codec: Codec<A>, options?: RegisterOptions) => void
384
+ }
385
+
358
386
  /**
359
387
  * Returns the array OID whose elements have the given OID, or `undefined`
360
388
  * when there is no array type registered for it.
@@ -362,7 +390,8 @@ const elementToArray = new Map<number, number>(
362
390
  * @category getters
363
391
  * @since 4.0.0
364
392
  */
365
- export const arrayOidFor = (elementOid: number): number | undefined => elementToArray.get(elementOid)
393
+ export const arrayOidFor = (elementOid: number, registry?: Registry): number | undefined =>
394
+ registry === undefined ? elementToArray.get(elementOid) : getRegistryState(registry).elementToArray.get(elementOid)
366
395
 
367
396
  // -----------------------------------------------------------------------------
368
397
  // calendar helpers
@@ -629,123 +658,28 @@ const decodeNumeric = (bytes: Uint8Array, offset: number, size: number): string
629
658
  const PGSQL_AF_INET = 2
630
659
  const PGSQL_AF_INET6 = 3
631
660
 
632
- const parseIPv4 = (text: string): Uint8Array | undefined => {
633
- const parts = text.split(".")
634
- if (parts.length !== 4) return undefined
635
- const bytes = new Uint8Array(4)
636
- for (let i = 0; i < 4; i++) {
637
- if (!/^\d{1,3}$/.test(parts[i])) return undefined
638
- const value = Number(parts[i])
639
- if (value > 255) return undefined
640
- bytes[i] = value
641
- }
642
- return bytes
643
- }
644
-
645
- const parseIPv6 = (text: string): Uint8Array | undefined => {
646
- const halves = text.split("::")
647
- if (halves.length > 2) return undefined
648
- const toGroups = (part: string): Array<string> => part === "" ? [] : part.split(":")
649
- const head = toGroups(halves[0])
650
- const tail = halves.length === 2 ? toGroups(halves[1]) : []
651
- const bytes = new Uint8Array(16)
652
-
653
- const trailing = tail.length > 0 ? tail[tail.length - 1] : head.length > 0 ? head[head.length - 1] : ""
654
- const embedded = trailing.includes(".") ? parseIPv4(trailing) : undefined
655
- if (trailing.includes(".") && embedded === undefined) return undefined
656
- if (embedded !== undefined) {
657
- if (tail.length > 0) tail.pop()
658
- else head.pop()
659
- }
660
- const groupCount = embedded === undefined ? 8 : 6
661
- if (head.length + tail.length > groupCount) return undefined
662
- if (halves.length === 1 && head.length !== groupCount) return undefined
663
-
664
- const writeGroup = (group: string, offset: number): boolean => {
665
- if (!/^[0-9a-fA-F]{1,4}$/.test(group)) return false
666
- const value = Number.parseInt(group, 16)
667
- bytes[offset] = value >> 8
668
- bytes[offset + 1] = value & 0xff
669
- return true
670
- }
671
- for (let i = 0; i < head.length; i++) {
672
- if (!writeGroup(head[i], i * 2)) return undefined
673
- }
674
- for (let i = 0; i < tail.length; i++) {
675
- if (!writeGroup(tail[i], (groupCount - tail.length + i) * 2)) return undefined
676
- }
677
- if (embedded !== undefined) bytes.set(embedded, 12)
678
- return bytes
679
- }
680
-
681
- const formatIPv4 = (bytes: Uint8Array, offset: number): string =>
682
- `${bytes[offset]}.${bytes[offset + 1]}.${bytes[offset + 2]}.${bytes[offset + 3]}`
683
-
684
- const formatIPv6 = (bytes: Uint8Array, offset: number): string => {
685
- let isV4Mapped = bytes[offset + 10] === 0xff && bytes[offset + 11] === 0xff
686
- for (let i = 0; isV4Mapped && i < 10; i++) isV4Mapped = bytes[offset + i] === 0
687
- if (isV4Mapped) return `::ffff:${formatIPv4(bytes, offset + 12)}`
688
-
689
- const groups: Array<number> = []
690
- for (let i = 0; i < 8; i++) groups.push(readUint16(bytes, offset + i * 2))
691
-
692
- let bestStart = -1
693
- let bestLength = 0
694
- let start = -1
695
- for (let i = 0; i <= 8; i++) {
696
- if (i < 8 && groups[i] === 0) {
697
- if (start === -1) start = i
698
- } else if (start !== -1) {
699
- if (i - start > bestLength) {
700
- bestStart = start
701
- bestLength = i - start
702
- }
703
- start = -1
704
- }
705
- }
706
- if (bestLength < 2) {
707
- return groups.map((group) => group.toString(16)).join(":")
708
- }
709
- const head = groups.slice(0, bestStart).map((group) => group.toString(16)).join(":")
710
- const tail = groups.slice(bestStart + bestLength).map((group) => group.toString(16)).join(":")
711
- return `${head}::${tail}`
712
- }
713
-
714
- const hasHostBits = (bytes: Uint8Array, offset: number, length: number, bits: number): boolean => {
715
- const wholeBytes = Math.floor(bits / 8)
716
- const partialBits = bits % 8
717
- if (partialBits !== 0 && (bytes[offset + wholeBytes] & ((1 << (8 - partialBits)) - 1)) !== 0) {
718
- return true
719
- }
720
- for (let index = wholeBytes + (partialBits === 0 ? 0 : 1); index < length; index++) {
721
- if (bytes[offset + index] !== 0) return true
722
- }
723
- return false
724
- }
725
-
726
661
  const encodeInet = (value: unknown, isCidr: boolean): Uint8Array => {
727
- const text = requireString(value, isCidr ? "cidr" : "inet")
728
- const slash = text.lastIndexOf("/")
729
- const address = slash === -1 ? text : text.slice(0, slash)
730
- const v4 = parseIPv4(address)
731
- const bytes = v4 ?? parseIPv6(address)
732
- if (bytes === undefined) {
733
- return fail(`Expected an IP address, received "${text}"`)
734
- }
735
- const fullBits = bytes.length * 8
736
- const bits = slash === -1 ? fullBits : Number(text.slice(slash + 1))
737
- if (!Number.isInteger(bits) || bits < 0 || bits > fullBits) {
738
- return fail(`Invalid netmask length in "${text}"`)
739
- }
740
- if (isCidr && hasHostBits(bytes, 0, bytes.length, bits)) {
741
- return fail(`CIDR address has host bits set in "${text}"`)
662
+ const type = isCidr ? "cidr" : "inet"
663
+ const text = requireString(value, type)
664
+ const parsed = IpInterface.fromString(text)
665
+ if (Result.isFailure(parsed)) return fail(`Invalid ${type} value ${JSON.stringify(text)}: ${parsed.failure.message}`)
666
+ const address = parsed.success.address
667
+ const bits = parsed.success.prefixLength
668
+ if (isCidr) {
669
+ const network = IpNetwork.make(address, bits)
670
+ if (Result.isFailure(network)) {
671
+ return fail(`Invalid ${type} value ${JSON.stringify(text)}: ${network.failure.message}`)
672
+ }
742
673
  }
743
- const result = new Uint8Array(4 + bytes.length)
744
- result[0] = v4 === undefined ? PGSQL_AF_INET6 : PGSQL_AF_INET
674
+ const octets = NetAddress.isIpv4Address(address)
675
+ ? NetAddress.ipv4ToOctets(address)
676
+ : NetAddress.ipv6ToOctets(address)
677
+ const result = new Uint8Array(4 + octets.length)
678
+ result[0] = NetAddress.isIpv4Address(address) ? PGSQL_AF_INET : PGSQL_AF_INET6
745
679
  result[1] = bits
746
680
  result[2] = isCidr ? 1 : 0
747
- result[3] = bytes.length
748
- result.set(bytes, 4)
681
+ result[3] = octets.length
682
+ result.set(octets, 4)
749
683
  return result
750
684
  }
751
685
 
@@ -763,11 +697,18 @@ const decodeInet = (bytes: Uint8Array, offset: number, size: number): string =>
763
697
  }
764
698
  requireSize(size, 4 + addressSize, "inet")
765
699
  if (bits > addressSize * 8) return fail(`Invalid inet netmask length: ${bits}`)
766
- if (isCidr && hasHostBits(bytes, offset + 4, addressSize, bits)) {
767
- return fail("CIDR address has host bits set")
768
- }
769
- const text = family === PGSQL_AF_INET ? formatIPv4(bytes, offset + 4) : formatIPv6(bytes, offset + 4)
770
- return isCidr || bits !== addressSize * 8 ? `${text}/${bits}` : text
700
+ const addressBytes = bytes.slice(offset + 4, offset + 4 + addressSize)
701
+ const address = family === PGSQL_AF_INET
702
+ ? NetAddress.ipv4FromBytesUnsafe(addressBytes)
703
+ : NetAddress.ipv6FromBytesUnsafe(addressBytes)
704
+ if (isCidr) {
705
+ const network = IpNetwork.make(address, bits)
706
+ if (Result.isFailure(network)) return fail(network.failure.message)
707
+ return IpNetwork.format(network.success)
708
+ }
709
+ return bits === addressSize * 8
710
+ ? NetAddress.formatIp(address)
711
+ : IpInterface.format(IpInterface.makeUnsafe(address, bits))
771
712
  }
772
713
 
773
714
  // -----------------------------------------------------------------------------
@@ -956,6 +897,34 @@ interface UnsafeCodec<A> {
956
897
  readonly read?: (bytes: Uint8Array, offset: number, size: number) => A
957
898
  }
958
899
 
900
+ type Lookup = (oid: number) => UnsafeCodec<any> | undefined
901
+
902
+ const toUnsafeCodec = <A>(codec: Codec<A>): UnsafeCodec<A> => ({
903
+ encode(value) {
904
+ const encoded = codec.encode(value)
905
+ if (Result.isFailure(encoded)) throw encoded.failure
906
+ return encoded.success
907
+ },
908
+ decode(bytes) {
909
+ const decoded = codec.decode(bytes)
910
+ if (Result.isFailure(decoded)) throw decoded.failure
911
+ return decoded.success
912
+ },
913
+ ...(codec.write === undefined ? undefined : {
914
+ write(sink: ValueSink, value: A) {
915
+ const written = codec.write!(sink, value)
916
+ if (Result.isFailure(written)) throw written.failure
917
+ }
918
+ }),
919
+ ...(codec.read === undefined ? undefined : {
920
+ read(bytes: Uint8Array, offset: number, size: number) {
921
+ const decoded = codec.read!(bytes, offset, size)
922
+ if (Result.isFailure(decoded)) throw decoded.failure
923
+ return decoded.success
924
+ }
925
+ })
926
+ })
927
+
959
928
  /** A codec whose `decode` is its `read` over the whole of its bytes. */
960
929
  const codecOf = <A>(
961
930
  read: (bytes: Uint8Array, offset: number, size: number) => A,
@@ -1181,7 +1150,7 @@ const jsonbCodec: UnsafeCodec<any> = codecOf(
1181
1150
  }
1182
1151
  )
1183
1152
 
1184
- const builtins = new Map<number, UnsafeCodec<any>>([
1153
+ const builtinScalars = new Map<number, UnsafeCodec<any>>([
1185
1154
  [
1186
1155
  OID.bool,
1187
1156
  codecOf(
@@ -1340,16 +1309,14 @@ const builtins = new Map<number, UnsafeCodec<any>>([
1340
1309
  [OID.timestamptz, timestampCodec]
1341
1310
  ])
1342
1311
 
1343
- for (const [arrayOid, elementOid] of arrayToElement) {
1344
- builtins.set(
1345
- arrayOid,
1346
- codecOf(
1347
- (bytes, offset, size) => decodeArray(bytes, offset, size, elementOid),
1348
- (value) => encodeArray(value, elementOid),
1349
- (sink, value) => writeArray(sink, value, elementOid)
1350
- )
1312
+ const makeArrayCodec = (elementOid: number, lookup: Lookup): UnsafeCodec<ReadonlyArray<unknown>> =>
1313
+ codecOf(
1314
+ (bytes, offset, size) => decodeArray(bytes, offset, size, elementOid, lookup),
1315
+ (value) => encodeArray(value, elementOid, lookup),
1316
+ (sink, value) => writeArray(sink, value, elementOid, lookup)
1351
1317
  )
1352
- }
1318
+
1319
+ const builtins = new Map<number, UnsafeCodec<any>>(builtinScalars)
1353
1320
 
1354
1321
  /**
1355
1322
  * Built-ins with registered codecs layered over them, so `encode` and `decode`
@@ -1365,10 +1332,69 @@ const codecs = new Map<number, UnsafeCodec<any>>(builtins)
1365
1332
  const tableSize = 4096
1366
1333
 
1367
1334
  const table = new Array<UnsafeCodec<any> | undefined>(tableSize)
1368
- for (const [oid, codec] of codecs) table[oid] = codec
1369
1335
 
1370
1336
  const lookup = (oid: number): UnsafeCodec<any> | undefined => oid >= 0 && oid < tableSize ? table[oid] : codecs.get(oid)
1371
1337
 
1338
+ for (const [arrayOid, elementOid] of arrayToElement) {
1339
+ const codec = makeArrayCodec(elementOid, lookup)
1340
+ builtins.set(arrayOid, codec)
1341
+ codecs.set(arrayOid, codec)
1342
+ }
1343
+ for (const [oid, codec] of codecs) table[oid] = codec
1344
+
1345
+ interface RegistryState {
1346
+ readonly codecs: Map<number, UnsafeCodec<any>>
1347
+ readonly elementToArray: Map<number, number>
1348
+ readonly lookup: Lookup
1349
+ }
1350
+
1351
+ const registryStates = new WeakMap<Registry, RegistryState>()
1352
+
1353
+ const getRegistryState = (registry: Registry): RegistryState => {
1354
+ const state = registryStates.get(registry)
1355
+ if (state === undefined) return fail("Invalid PgTypes Registry")
1356
+ return state
1357
+ }
1358
+
1359
+ const registerInState = <A>(
1360
+ state: RegistryState,
1361
+ oid: number,
1362
+ codec: Codec<A>,
1363
+ options?: RegisterOptions
1364
+ ): void => {
1365
+ state.codecs.set(oid, toUnsafeCodec(codec))
1366
+ if (options?.arrayOid !== undefined) {
1367
+ state.elementToArray.set(oid, options.arrayOid)
1368
+ state.codecs.set(options.arrayOid, makeArrayCodec(oid, state.lookup))
1369
+ }
1370
+ }
1371
+
1372
+ /**
1373
+ * Creates a client-specific registry containing the built-in codecs.
1374
+ *
1375
+ * @category constructors
1376
+ * @since 4.0.0
1377
+ */
1378
+ export const makeRegistry = (): Registry => {
1379
+ const codecs = new Map<number, UnsafeCodec<any>>(builtinScalars)
1380
+ const state: RegistryState = {
1381
+ codecs,
1382
+ elementToArray: new Map(elementToArray),
1383
+ lookup: (oid) => codecs.get(oid)
1384
+ }
1385
+ for (const [arrayOid, elementOid] of arrayToElement) {
1386
+ codecs.set(arrayOid, makeArrayCodec(elementOid, state.lookup))
1387
+ }
1388
+ const registry: Registry = {
1389
+ register: (oid, codec, options) => registerInState(state, oid, codec, options)
1390
+ }
1391
+ registryStates.set(registry, state)
1392
+ return registry
1393
+ }
1394
+
1395
+ const lookupFor = (registry: Registry | undefined): Lookup =>
1396
+ registry === undefined ? lookup : getRegistryState(registry).lookup
1397
+
1372
1398
  /**
1373
1399
  * Registers a binary codec for an OID the built-in catalogue does not cover,
1374
1400
  * or overrides a built-in one. Registered codecs take precedence.
@@ -1377,31 +1403,7 @@ const lookup = (oid: number): UnsafeCodec<any> | undefined => oid >= 0 && oid <
1377
1403
  * @since 4.0.0
1378
1404
  */
1379
1405
  export const register = <A>(oid: number, codec: Codec<A>): void => {
1380
- const unsafe: UnsafeCodec<A> = {
1381
- encode(value) {
1382
- const encoded = codec.encode(value)
1383
- if (Result.isFailure(encoded)) throw encoded.failure
1384
- return encoded.success
1385
- },
1386
- decode(bytes) {
1387
- const decoded = codec.decode(bytes)
1388
- if (Result.isFailure(decoded)) throw decoded.failure
1389
- return decoded.success
1390
- },
1391
- ...(codec.write === undefined ? undefined : {
1392
- write(sink: ValueSink, value: A) {
1393
- const written = codec.write!(sink, value)
1394
- if (Result.isFailure(written)) throw written.failure
1395
- }
1396
- }),
1397
- ...(codec.read === undefined ? undefined : {
1398
- read(bytes: Uint8Array, offset: number, size: number) {
1399
- const decoded = codec.read!(bytes, offset, size)
1400
- if (Result.isFailure(decoded)) throw decoded.failure
1401
- return decoded.success
1402
- }
1403
- })
1404
- }
1406
+ const unsafe = toUnsafeCodec(codec)
1405
1407
  codecs.set(oid, unsafe)
1406
1408
  if (oid >= 0 && oid < tableSize) table[oid] = unsafe
1407
1409
  }
@@ -1423,7 +1425,7 @@ export const unregister = (oid: number): void => {
1423
1425
  // arrays
1424
1426
  // -----------------------------------------------------------------------------
1425
1427
 
1426
- const encodeArray = (value: unknown, elementOid: number): Uint8Array => {
1428
+ const encodeArray = (value: unknown, elementOid: number, lookup: Lookup): Uint8Array => {
1427
1429
  if (!Array.isArray(value)) {
1428
1430
  return fail("Expected an array")
1429
1431
  }
@@ -1475,7 +1477,7 @@ const encodeArray = (value: unknown, elementOid: number): Uint8Array => {
1475
1477
  * and written in place, so neither the elements nor the array itself needs an
1476
1478
  * array of bytes of its own.
1477
1479
  */
1478
- const writeArray = (sink: ValueSink, value: unknown, elementOid: number): void => {
1480
+ const writeArray = (sink: ValueSink, value: unknown, elementOid: number, lookup: Lookup): void => {
1479
1481
  if (!Array.isArray(value)) {
1480
1482
  return fail("Expected an array")
1481
1483
  }
@@ -1506,7 +1508,7 @@ const writeArray = (sink: ValueSink, value: unknown, elementOid: number): void =
1506
1508
  sink.int32(-1)
1507
1509
  } else {
1508
1510
  const token = sink.beginLength()
1509
- if (write === undefined) writeValue(sink, element, elementOid)
1511
+ if (write === undefined) writeValue(sink, element, elementOid, lookup)
1510
1512
  else write(sink, element)
1511
1513
  sink.endLength(token)
1512
1514
  }
@@ -1522,7 +1524,8 @@ const decodeArray = (
1522
1524
  bytes: Uint8Array,
1523
1525
  start: number,
1524
1526
  size: number,
1525
- elementOid: number
1527
+ elementOid: number,
1528
+ lookup: Lookup
1526
1529
  ): ReadonlyArray<unknown> => {
1527
1530
  if (size < 12) return fail("Truncated array value")
1528
1531
  const dimensions = readInt32(bytes, start)
@@ -1584,18 +1587,15 @@ export interface Column {
1584
1587
  }
1585
1588
 
1586
1589
  /**
1587
- * Builds a field reader for `PgProtocol.makeParser`, so a result's rows decode
1588
- * as they are parsed rather than through a view per column.
1590
+ * Creates a field reader for `PgProtocol.makeParser`.
1591
+ *
1592
+ * **Details**
1589
1593
  *
1590
- * Every column is resolved once here rather than once per row, and a codec that
1591
- * can read in place does; the rest are handed a view. SQL NULL reads as `null`,
1592
- * and a column whose OID has no codec reads as a copy of its bytes.
1594
+ * Codecs are resolved once per column. SQL `NULL` becomes `null`, and columns
1595
+ * without a registered codec return a copy of their bytes. Text-format columns
1596
+ * fail with `CodecError`.
1593
1597
  *
1594
- * Only the binary format is supported, and a text column returns a
1595
- * `CodecError` failure here rather than once per row. A successful result
1596
- * contains the parser's internal throwing fast path; its failures are terminal
1597
- * for that parser. The standalone `encode` and `decode` APIs remain typed
1598
- * `Result` values.
1598
+ * **Example** (Updating the reader after `RowDescription`)
1599
1599
  *
1600
1600
  * ```ts
1601
1601
  * import { PgProtocol, PgTypes } from "@effect/sql-pg"
@@ -1610,9 +1610,11 @@ export interface Column {
1610
1610
  * @since 4.0.0
1611
1611
  */
1612
1612
  export const makeFieldReader = (
1613
- columns: ReadonlyArray<Column>
1613
+ columns: ReadonlyArray<Column>,
1614
+ registry?: Registry
1614
1615
  ): Result.Result<PgProtocol.FieldReader<unknown>, CodecError> =>
1615
1616
  result(() => {
1617
+ const lookup = lookupFor(registry)
1616
1618
  const codecs = columns.map((column, index) => {
1617
1619
  if (column.format !== 1) {
1618
1620
  return fail(`Only the binary format is supported, column ${index} has format ${column.format}`)
@@ -1631,15 +1633,17 @@ export const makeFieldReader = (
1631
1633
  /**
1632
1634
  * Encodes a JavaScript value as the binary representation of the given OID.
1633
1635
  *
1636
+ * **Details**
1637
+ *
1634
1638
  * Returns a `CodecError` failure when the value has the wrong JavaScript type,
1635
1639
  * or when the OID is neither built in nor registered.
1636
1640
  *
1637
1641
  * @category encoding
1638
1642
  * @since 4.0.0
1639
1643
  */
1640
- export const encode = (value: unknown, oid: number): Result.Result<Uint8Array, CodecError> =>
1644
+ export const encode = (value: unknown, oid: number, registry?: Registry): Result.Result<Uint8Array, CodecError> =>
1641
1645
  result(() => {
1642
- const codec = lookup(oid)
1646
+ const codec = lookupFor(registry)(oid)
1643
1647
  if (codec === undefined) return fail(`No codec registered for OID ${oid}`)
1644
1648
  return codec.encode(value)
1645
1649
  })
@@ -1647,6 +1651,8 @@ export const encode = (value: unknown, oid: number): Result.Result<Uint8Array, C
1647
1651
  /**
1648
1652
  * Decodes the binary representation of the given OID.
1649
1653
  *
1654
+ * **Details**
1655
+ *
1650
1656
  * `format` must be `1`; the text format is not implemented. An OID that is
1651
1657
  * neither built in nor registered decodes to the raw bytes.
1652
1658
  *
@@ -1656,13 +1662,14 @@ export const encode = (value: unknown, oid: number): Result.Result<Uint8Array, C
1656
1662
  export const decode = (
1657
1663
  bytes: Uint8Array,
1658
1664
  oid: number,
1659
- format: number
1665
+ format: number,
1666
+ registry?: Registry
1660
1667
  ): Result.Result<unknown, CodecError> =>
1661
1668
  result(() => {
1662
1669
  if (format !== 1) {
1663
1670
  return fail(`Only the binary format is supported, received format ${format}`)
1664
1671
  }
1665
- const codec = lookup(oid)
1672
+ const codec = lookupFor(registry)(oid)
1666
1673
  return codec === undefined ? bytes : codec.decode(bytes)
1667
1674
  })
1668
1675
 
@@ -1670,6 +1677,22 @@ export const decode = (
1670
1677
  // parameter constructors
1671
1678
  // -----------------------------------------------------------------------------
1672
1679
 
1680
+ /**
1681
+ * The runtime type identifier for PostgreSQL parameters.
1682
+ *
1683
+ * @category type IDs
1684
+ * @since 4.0.0
1685
+ */
1686
+ export const ParameterTypeId: ParameterTypeId = "~@effect/sql-pg/PgTypes/Parameter"
1687
+
1688
+ /**
1689
+ * The type-level identifier for PostgreSQL parameters.
1690
+ *
1691
+ * @category type IDs
1692
+ * @since 4.0.0
1693
+ */
1694
+ export type ParameterTypeId = "~@effect/sql-pg/PgTypes/Parameter"
1695
+
1673
1696
  /**
1674
1697
  * A value paired with the OID it should be encoded as.
1675
1698
  *
@@ -1677,26 +1700,49 @@ export const decode = (
1677
1700
  * @since 4.0.0
1678
1701
  */
1679
1702
  export interface Parameter {
1703
+ readonly [ParameterTypeId]: ParameterTypeId
1680
1704
  readonly oid: number
1681
1705
  readonly value: unknown
1682
1706
  }
1683
1707
 
1708
+ /**
1709
+ * Returns whether a value is a parameter created by this module.
1710
+ *
1711
+ * @category guards
1712
+ * @since 4.0.0
1713
+ */
1714
+ export const isParameter = (value: unknown): value is Parameter =>
1715
+ typeof value === "object" && value !== null && (value as any)[ParameterTypeId] === ParameterTypeId
1716
+
1684
1717
  /**
1685
1718
  * Encodes a parameter for a `Bind` message. SQL NULL stays `null`.
1686
1719
  *
1687
1720
  * @category encoding
1688
1721
  * @since 4.0.0
1689
1722
  */
1690
- export const encodeParameter = (parameter: Parameter): Result.Result<Uint8Array | null, CodecError> =>
1691
- parameter.value === null ? Result.succeed(null) : encode(parameter.value, parameter.oid)
1723
+ export const encodeParameter = (
1724
+ parameter: Parameter,
1725
+ registry?: Registry
1726
+ ): Result.Result<Uint8Array | null, CodecError> =>
1727
+ parameter.value === null ? Result.succeed(null) : encode(parameter.value, parameter.oid, registry)
1692
1728
 
1693
- const writeValue = (sink: ValueSink, value: unknown, oid: number): void => {
1729
+ const writeValue = (sink: ValueSink, value: unknown, oid: number, lookup: Lookup): void => {
1694
1730
  const codec = lookup(oid)
1695
1731
  if (codec === undefined) return fail(`No codec registered for OID ${oid}`)
1696
1732
  if (codec.write === undefined) sink.raw(codec.encode(value))
1697
1733
  else codec.write(sink, value)
1698
1734
  }
1699
1735
 
1736
+ /**
1737
+ * Returns whether a parameter uses the text format in a `Bind` message.
1738
+ * Untyped parameters (OID `0`) use text so PostgreSQL can infer their type;
1739
+ * typed parameters use the binary format.
1740
+ *
1741
+ * @category encoding
1742
+ * @since 4.0.0
1743
+ */
1744
+ export const isTextFormat = (parameter: Parameter): boolean => parameter.oid === 0
1745
+
1700
1746
  /**
1701
1747
  * Writes a parameter into a `Bind` frame, for `PgProtocol.makeBindEncoder`.
1702
1748
  * Codecs that can write their bytes in place do; the rest fall back to
@@ -1705,15 +1751,28 @@ const writeValue = (sink: ValueSink, value: unknown, oid: number): void => {
1705
1751
  * @category encoding
1706
1752
  * @since 4.0.0
1707
1753
  */
1708
- const writeParameterUnsafe = (sink: ValueSink, parameter: Parameter): void =>
1709
- parameter.value === null ? sink.sqlNull() : writeValue(sink, parameter.value, parameter.oid)
1754
+ const writeParameterUnsafe = (sink: ValueSink, parameter: Parameter, lookup: Lookup): void =>
1755
+ parameter.value === null
1756
+ ? sink.sqlNull()
1757
+ // An untyped parameter is the value's text representation; see
1758
+ // `isTextFormat`.
1759
+ : parameter.oid === 0
1760
+ ? sink.utf8(String(parameter.value))
1761
+ : writeValue(sink, parameter.value, parameter.oid, lookup)
1710
1762
 
1763
+ /**
1764
+ * Writes a parameter into a `Bind` frame.
1765
+ *
1766
+ * @category encoding
1767
+ * @since 4.0.0
1768
+ */
1711
1769
  export const writeParameter = (
1712
1770
  sink: ValueSink,
1713
- parameter: Parameter
1771
+ parameter: Parameter,
1772
+ registry?: Registry
1714
1773
  ): Result.Result<void, CodecError> => {
1715
1774
  try {
1716
- writeParameterUnsafe(sink, parameter)
1775
+ writeParameterUnsafe(sink, parameter, lookupFor(registry))
1717
1776
  return Result.void
1718
1777
  } catch (error) {
1719
1778
  if (error instanceof CodecError) return Result.fail(error)
@@ -1722,9 +1781,17 @@ export const writeParameter = (
1722
1781
  }
1723
1782
 
1724
1783
  const valueWriterUnsafe = Symbol.for("@effect/sql-pg/PgProtocol/ValueWriter/unsafe")
1725
- Object.defineProperty(writeParameter, valueWriterUnsafe, { value: writeParameterUnsafe })
1784
+ Object.defineProperty(writeParameter, valueWriterUnsafe, {
1785
+ value: (sink: ValueSink, parameter: Parameter) => writeParameterUnsafe(sink, parameter, lookup)
1786
+ })
1787
+
1788
+ const makeParameter = (oid: number, value: unknown): Parameter => ({
1789
+ [ParameterTypeId]: ParameterTypeId,
1790
+ oid,
1791
+ value
1792
+ })
1726
1793
 
1727
- const parameter = (oid: number) => (value: unknown): Parameter => ({ oid, value })
1794
+ const parameter = (oid: number) => (value: unknown): Parameter => makeParameter(oid, value)
1728
1795
 
1729
1796
  /**
1730
1797
  * A `bool` parameter.
@@ -1919,12 +1986,13 @@ export const timestamptz: (value: number | null) => Parameter = parameter(OID.ti
1919
1986
  */
1920
1987
  export const array = (
1921
1988
  values: ReadonlyArray<unknown> | null,
1922
- elementOid: number
1989
+ elementOid: number,
1990
+ registry?: Registry
1923
1991
  ): Result.Result<Parameter, CodecError> =>
1924
1992
  result(() => {
1925
- const arrayOid = elementToArray.get(elementOid)
1993
+ const arrayOid = arrayOidFor(elementOid, registry)
1926
1994
  if (arrayOid === undefined) {
1927
1995
  return fail(`No array type known for element OID ${elementOid}`)
1928
1996
  }
1929
- return { oid: arrayOid, value: values }
1997
+ return makeParameter(arrayOid, values)
1930
1998
  })
package/src/index.ts CHANGED
@@ -14,11 +14,21 @@ export * as PgAuth from "./PgAuth.ts"
14
14
  */
15
15
  export * as PgClient from "./PgClient.ts"
16
16
 
17
+ /**
18
+ * @since 4.0.0
19
+ */
20
+ export * as PgConnection from "./PgConnection.ts"
21
+
17
22
  /**
18
23
  * @since 4.0.0
19
24
  */
20
25
  export * as PgMigrator from "./PgMigrator.ts"
21
26
 
27
+ /**
28
+ * @since 4.0.0
29
+ */
30
+ export * as PgPool from "./PgPool.ts"
31
+
22
32
  /**
23
33
  * @since 4.0.0
24
34
  */