@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.
- package/AGENTS.md +24 -9
- package/CLAUDE.md +24 -9
- package/ai-docs/package.json +2 -2
- package/ai-docs/src/01_effect/01_basics/02_effect-fn.ts +18 -5
- package/ai-docs/src/01_effect/01_basics/index.md +5 -3
- package/ai-docs/src/01_effect/03_services/20_layer-composition.ts +1 -1
- package/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts +2 -2
- package/ai-docs/src/01_effect/05_resources/10_acquire-release.ts +2 -2
- package/ai-docs/src/03_stream/30_encoding.ts +5 -7
- package/ai-docs/src/08_observability/10_logging.ts +1 -1
- package/ai-docs/src/70_cli/10_basics.ts +7 -7
- package/ai-docs/src/71_ai/10_language-model.ts +2 -2
- package/ai-docs/src/71_ai/20_tools.ts +1 -1
- package/ai-docs/src/71_ai/30_chat.ts +1 -1
- package/dist/PgAuth.d.ts +24 -0
- package/dist/PgAuth.d.ts.map +1 -1
- package/dist/PgAuth.js +29 -2
- package/dist/PgAuth.js.map +1 -1
- package/dist/PgClient.d.ts +49 -82
- package/dist/PgClient.d.ts.map +1 -1
- package/dist/PgClient.js +58 -510
- package/dist/PgClient.js.map +1 -1
- package/dist/PgConnection.d.ts +177 -0
- package/dist/PgConnection.d.ts.map +1 -0
- package/dist/PgConnection.js +1786 -0
- package/dist/PgConnection.js.map +1 -0
- package/dist/PgPool.d.ts +107 -0
- package/dist/PgPool.d.ts.map +1 -0
- package/dist/PgPool.js +124 -0
- package/dist/PgPool.js.map +1 -0
- package/dist/PgProtocol.d.ts +63 -30
- package/dist/PgProtocol.d.ts.map +1 -1
- package/dist/PgProtocol.js +64 -22
- package/dist/PgProtocol.js.map +1 -1
- package/dist/PgTypes.d.ts +85 -17
- package/dist/PgTypes.d.ts.map +1 -1
- package/dist/PgTypes.js +166 -169
- package/dist/PgTypes.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/connection.d.ts +2 -0
- package/dist/internal/connection.d.ts.map +1 -0
- package/dist/internal/connection.js +5 -0
- package/dist/internal/connection.js.map +1 -0
- package/dist/internal/sqlError.d.ts +10 -0
- package/dist/internal/sqlError.d.ts.map +1 -0
- package/dist/internal/sqlError.js +57 -0
- package/dist/internal/sqlError.js.map +1 -0
- package/package.json +6 -14
- package/src/PgAuth.ts +33 -2
- package/src/PgClient.ts +138 -681
- package/src/PgConnection.ts +2294 -0
- package/src/PgPool.ts +231 -0
- package/src/PgProtocol.ts +93 -50
- package/src/PgTypes.ts +254 -186
- package/src/index.ts +10 -0
- package/src/internal/connection.ts +26 -0
- 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 =>
|
|
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
|
|
728
|
-
const
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
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
|
|
744
|
-
|
|
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] =
|
|
748
|
-
result.set(
|
|
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
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
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
|
|
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
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
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
|
|
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
|
-
*
|
|
1588
|
-
*
|
|
1590
|
+
* Creates a field reader for `PgProtocol.makeParser`.
|
|
1591
|
+
*
|
|
1592
|
+
* **Details**
|
|
1589
1593
|
*
|
|
1590
|
-
*
|
|
1591
|
-
*
|
|
1592
|
-
*
|
|
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
|
-
*
|
|
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 =
|
|
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 =
|
|
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 = (
|
|
1691
|
-
parameter
|
|
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
|
|
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, {
|
|
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 => (
|
|
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 =
|
|
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
|
|
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
|
*/
|