@effect/sql-pg 4.0.0-rc.111 → 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 +93 -0
  16. package/dist/PgAuth.d.ts.map +1 -0
  17. package/dist/PgAuth.js +229 -0
  18. package/dist/PgAuth.js.map +1 -0
  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 +783 -0
  32. package/dist/PgProtocol.d.ts.map +1 -0
  33. package/dist/PgProtocol.js +1202 -0
  34. package/dist/PgProtocol.js.map +1 -0
  35. package/dist/PgTypes.d.ts +429 -0
  36. package/dist/PgTypes.d.ts.map +1 -0
  37. package/dist/PgTypes.js +1511 -0
  38. package/dist/PgTypes.js.map +1 -0
  39. package/dist/index.d.ts +20 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +20 -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 +8 -12
  52. package/src/PgAuth.ts +318 -0
  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 +1863 -0
  57. package/src/PgTypes.ts +1998 -0
  58. package/src/index.ts +25 -0
  59. package/src/internal/connection.ts +26 -0
  60. package/src/internal/sqlError.ts +75 -0
package/src/PgTypes.ts ADDED
@@ -0,0 +1,1998 @@
1
+ /**
2
+ * Binary codecs for PostgreSQL values, keyed by type OID.
3
+ *
4
+ * Version 1 implements the binary wire format (`format = 1`) only; passing
5
+ * `format = 0` to `decode` is an error. Layouts follow rust-postgres'
6
+ * `postgres-types`, including the infinity sentinels, and assume the server
7
+ * was built with `integer_datetimes` (the only supported configuration since
8
+ * PostgreSQL 10).
9
+ *
10
+ * There is no `typeof` inference: an OID is always supplied, either directly
11
+ * or through a constructor such as `int4` that carries it.
12
+ *
13
+ * `timestamp` has no time zone on the wire and is treated as UTC in both
14
+ * directions. Decoding drops sub-millisecond precision by truncating toward
15
+ * zero, including for timestamps before the PostgreSQL epoch.
16
+ *
17
+ * @since 4.0.0
18
+ */
19
+ import * as Data from "effect/Data"
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"
24
+ import type * as PgProtocol from "./PgProtocol.ts"
25
+ import type { ValueSink } from "./PgProtocol.ts"
26
+
27
+ /**
28
+ * Failure returned when a value cannot be encoded or decoded for its OID.
29
+ *
30
+ * @category errors
31
+ * @since 4.0.0
32
+ */
33
+ export class CodecError extends Data.TaggedError("PgTypesCodecError")<{
34
+ readonly message: string
35
+ }> {}
36
+
37
+ const fail = (message: string): never => {
38
+ throw new CodecError({ message })
39
+ }
40
+
41
+ const result = <A>(evaluate: () => A): Result.Result<A, CodecError> => {
42
+ try {
43
+ return Result.succeed(evaluate())
44
+ } catch (error) {
45
+ if (error instanceof CodecError) return Result.fail(error)
46
+ throw error
47
+ }
48
+ }
49
+
50
+ const textEncoder = new TextEncoder()
51
+ const textDecoder = new TextDecoder("utf-8", { fatal: true })
52
+
53
+ const writeInt16 = (bytes: Uint8Array, offset: number, value: number): void => {
54
+ bytes[offset] = value >>> 8
55
+ bytes[offset + 1] = value
56
+ }
57
+
58
+ const readInt16 = (bytes: Uint8Array, offset: number): number => ((bytes[offset] << 8) | bytes[offset + 1]) << 16 >> 16
59
+
60
+ const readUint16 = (bytes: Uint8Array, offset: number): number => (bytes[offset] << 8) | bytes[offset + 1]
61
+
62
+ const writeInt32 = (bytes: Uint8Array, offset: number, value: number): void => {
63
+ bytes[offset] = value >>> 24
64
+ bytes[offset + 1] = value >>> 16
65
+ bytes[offset + 2] = value >>> 8
66
+ bytes[offset + 3] = value
67
+ }
68
+
69
+ const readInt32 = (bytes: Uint8Array, offset: number): number =>
70
+ (bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3]
71
+
72
+ /** Writes an int64, given a value the caller has checked fits a double exactly. */
73
+ const writeInt64 = (bytes: Uint8Array, offset: number, value: number): void => {
74
+ const high = Math.floor(value / 4294967296)
75
+ writeInt32(bytes, offset, high)
76
+ writeInt32(bytes, offset + 4, value - high * 4294967296)
77
+ }
78
+
79
+ const readUint32 = (bytes: Uint8Array, offset: number): number =>
80
+ ((bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3]) >>> 0
81
+
82
+ /**
83
+ * Constructing a `DataView` costs more than the reads and writes it performs,
84
+ * so the fixed-width codecs stage values through these instead of wrapping
85
+ * every freshly allocated array.
86
+ */
87
+ const scratch4 = new ArrayBuffer(4)
88
+ const scratchView4 = new DataView(scratch4)
89
+ const scratchBytes4 = new Uint8Array(scratch4)
90
+ const scratch8 = new ArrayBuffer(8)
91
+ const scratchView8 = new DataView(scratch8)
92
+ const scratchBytes8 = new Uint8Array(scratch8)
93
+
94
+ /**
95
+ * Stages eight wire bytes into `scratchView8`. Two big-endian `setInt32` calls
96
+ * beat both `Uint8Array.prototype.set` and eight stores, and unlike `set` they
97
+ * read from anywhere in `bytes` without a view of their own.
98
+ */
99
+ const stage8 = (bytes: Uint8Array, offset: number): void => {
100
+ scratchView8.setInt32(0, readInt32(bytes, offset))
101
+ scratchView8.setInt32(4, readInt32(bytes, offset + 4))
102
+ }
103
+
104
+ /** Decimal text for every two-digit number, so formatting never calls `padStart`. */
105
+ const twoDigits = /* @__PURE__ */ Array.from({ length: 100 }, (_, value) => (value < 10 ? "0" : "") + value)
106
+
107
+ /**
108
+ * Above this length `TextEncoder.encodeInto` beats a per-character loop, below
109
+ * it the call overhead dominates. Measured on V8: the loop runs at about a
110
+ * nanosecond per character and `encodeInto` costs about 50 ns whatever the
111
+ * length, so the crossover is around 50 characters.
112
+ */
113
+ const asciiEncodeLimit = 48
114
+
115
+ /**
116
+ * `encodeInto` needs somewhere to write. Encoding through this and copying out
117
+ * still beats `TextEncoder.encode`, which allocates inside the call. Strings
118
+ * that would need a larger one are rare enough to encode the slow way rather
119
+ * than keep that much memory alive.
120
+ */
121
+ let scratchUtf8 = new Uint8Array(4096)
122
+
123
+ const scratchUtf8Limit = 64 * 1024
124
+
125
+ /** Encodes into `scratchUtf8`, returning how many bytes it holds, or -1. */
126
+ const encodeUtf8Scratch = (text: string): number => {
127
+ const capacity = text.length * 3
128
+ if (capacity > scratchUtf8Limit) return -1
129
+ if (capacity > scratchUtf8.length) scratchUtf8 = new Uint8Array(capacity)
130
+ return textEncoder.encodeInto(text, scratchUtf8).written
131
+ }
132
+
133
+ const encodeUtf8 = (text: string): Uint8Array => {
134
+ const length = text.length
135
+ if (length <= asciiEncodeLimit) {
136
+ const bytes = new Uint8Array(length)
137
+ let i = 0
138
+ for (; i < length; i++) {
139
+ const code = text.charCodeAt(i)
140
+ if (code > 0x7f) break
141
+ bytes[i] = code
142
+ }
143
+ if (i === length) return bytes
144
+ }
145
+ const written = encodeUtf8Scratch(text)
146
+ return written === -1 ? textEncoder.encode(text) : scratchUtf8.slice(0, written)
147
+ }
148
+
149
+ /** As `encodeUtf8`, but leaves `prefix` bytes free at the front. */
150
+ const encodeUtf8Prefixed = (text: string, prefix: number): Uint8Array => {
151
+ const length = text.length
152
+ if (length <= asciiEncodeLimit) {
153
+ const bytes = new Uint8Array(prefix + length)
154
+ let i = 0
155
+ for (; i < length; i++) {
156
+ const code = text.charCodeAt(i)
157
+ if (code > 0x7f) break
158
+ bytes[prefix + i] = code
159
+ }
160
+ if (i === length) return bytes
161
+ }
162
+ const written = encodeUtf8Scratch(text)
163
+ if (written === -1) {
164
+ const body = textEncoder.encode(text)
165
+ const bytes = new Uint8Array(prefix + body.length)
166
+ bytes.set(body, prefix)
167
+ return bytes
168
+ }
169
+ const bytes = new Uint8Array(prefix + written)
170
+ bytes.set(scratchUtf8.subarray(0, written), prefix)
171
+ return bytes
172
+ }
173
+
174
+ /**
175
+ * A view of `size` bytes at `offset`, or `bytes` itself when that is already
176
+ * the whole of it. Only the codecs that hand their bytes to something else
177
+ * need one; the rest read through `bytes` and `offset` directly.
178
+ */
179
+ const region = (bytes: Uint8Array, offset: number, size: number): Uint8Array =>
180
+ offset === 0 && size === bytes.length ? bytes : new Uint8Array(bytes.buffer, bytes.byteOffset + offset, size)
181
+
182
+ /**
183
+ * Below this length building the string a character at a time beats
184
+ * `TextDecoder.decode`, which costs about 50 ns before it looks at a byte.
185
+ * Above it the per-character cost of about 3 ns takes over.
186
+ */
187
+ const asciiDecodeLimit = 10
188
+
189
+ /**
190
+ * Node's own UTF-8 decoder, which reads straight out of the buffer with no
191
+ * view and about a quarter less overhead than `TextDecoder`. It swaps
192
+ * replacement characters in where `TextDecoder` would fail, so a result that
193
+ * contains one is handed to the strict decoder after all: either the
194
+ * character was really in the text and the same string comes back, or the
195
+ * bytes were invalid and the failure is the one `TextDecoder` always raised.
196
+ */
197
+ const utf8Slice: ((this: Uint8Array, start: number, end: number) => string) | undefined = (globalThis as any).Buffer
198
+ ?.prototype?.utf8Slice
199
+
200
+ const decodeUtf8 = (bytes: Uint8Array, offset: number, size: number): string => {
201
+ if (size <= asciiDecodeLimit) {
202
+ let text = ""
203
+ let index = 0
204
+ for (; index < size; index++) {
205
+ const code = bytes[offset + index]
206
+ if (code > 0x7f) break
207
+ text += String.fromCharCode(code)
208
+ }
209
+ if (index === size) return text
210
+ }
211
+ if (utf8Slice !== undefined) {
212
+ const text = utf8Slice.call(bytes, offset, offset + size)
213
+ if (text.indexOf("\ufffd") === -1) return text
214
+ }
215
+ try {
216
+ return textDecoder.decode(region(bytes, offset, size))
217
+ } catch {
218
+ return fail("Invalid UTF-8 in text value")
219
+ }
220
+ }
221
+
222
+ const requireSize = (size: number, expected: number, name: string): void => {
223
+ if (size !== expected) {
224
+ fail(`Expected ${expected} byte(s) for ${name}, received ${size}`)
225
+ }
226
+ }
227
+
228
+ const requireString = (value: unknown, name: string): string =>
229
+ typeof value === "string" ? value : fail(`Expected a string for ${name}`)
230
+
231
+ const requireBigInt = (value: unknown, name: string): bigint =>
232
+ typeof value === "bigint" ? value : fail(`Expected a bigint for ${name}`)
233
+
234
+ const requireNumber = (value: unknown, name: string): number =>
235
+ typeof value === "number" ? value : fail(`Expected a number for ${name}`)
236
+
237
+ const requireInteger = (value: unknown, name: string, min: number, max: number): number => {
238
+ const num = requireNumber(value, name)
239
+ if (!Number.isInteger(num) || num < min || num > max) {
240
+ fail(`Expected an integer in [${min}, ${max}] for ${name}, received ${num}`)
241
+ }
242
+ return num
243
+ }
244
+
245
+ const ZERO = BigInt(0)
246
+ const THOUSAND = BigInt(1000)
247
+ const INT64_MIN = BigInt("-9223372036854775808")
248
+ const INT64_MAX = BigInt("9223372036854775807")
249
+ const INT32_MIN = -2147483648
250
+ const INT32_MAX = 2147483647
251
+
252
+ /** Milliseconds between the Unix epoch and the PostgreSQL epoch (2000-01-01). */
253
+ const PG_EPOCH_MS = 946684800000
254
+ const PG_EPOCH_MICROS = 946684800000000
255
+ /** Days between the Unix epoch and the PostgreSQL epoch. */
256
+ const PG_EPOCH_DAYS = 10957
257
+
258
+ /**
259
+ * Beyond this an integer no longer has an exact `Number`, so the timestamp
260
+ * codecs fall back to `BigInt` rather than round. It is about 285 years of
261
+ * microseconds either side of 2000-01-01.
262
+ */
263
+ const MAX_EXACT = 9007199254740992
264
+ /** The `high` half of an int64 whose magnitude is below `MAX_EXACT`. */
265
+ const MAX_EXACT_HIGH = 0x200000
266
+
267
+ /** Midnight to midnight, the widest PostgreSQL `time` and `timetz` allow. */
268
+ const MAX_TIME_MICROS_NUMBER = 86_400_000_000
269
+ /** PostgreSQL accepts time zone displacements strictly inside +/-16 hours. */
270
+ const TZDISP_LIMIT_SECONDS = 57_600
271
+
272
+ // -----------------------------------------------------------------------------
273
+ // OIDs
274
+ // -----------------------------------------------------------------------------
275
+
276
+ /**
277
+ * Type OIDs implemented by version 1 of this codec.
278
+ *
279
+ * @category constants
280
+ * @since 4.0.0
281
+ */
282
+ export const OID = {
283
+ bool: 16,
284
+ bytea: 17,
285
+ name: 19,
286
+ int8: 20,
287
+ int2: 21,
288
+ int4: 23,
289
+ text: 25,
290
+ oid: 26,
291
+ json: 114,
292
+ cidr: 650,
293
+ float4: 700,
294
+ float8: 701,
295
+ inet: 869,
296
+ bpchar: 1042,
297
+ varchar: 1043,
298
+ date: 1082,
299
+ time: 1083,
300
+ timestamp: 1114,
301
+ timestamptz: 1184,
302
+ timetz: 1266,
303
+ numeric: 1700,
304
+ uuid: 2950,
305
+ jsonb: 3802,
306
+ boolArray: 1000,
307
+ byteaArray: 1001,
308
+ nameArray: 1003,
309
+ int8Array: 1016,
310
+ int2Array: 1005,
311
+ int4Array: 1007,
312
+ textArray: 1009,
313
+ oidArray: 1028,
314
+ jsonArray: 199,
315
+ cidrArray: 651,
316
+ float4Array: 1021,
317
+ float8Array: 1022,
318
+ inetArray: 1041,
319
+ bpcharArray: 1014,
320
+ varcharArray: 1015,
321
+ dateArray: 1182,
322
+ timeArray: 1183,
323
+ timestampArray: 1115,
324
+ timestamptzArray: 1185,
325
+ timetzArray: 1270,
326
+ numericArray: 1231,
327
+ uuidArray: 2951,
328
+ jsonbArray: 3807
329
+ } as const
330
+
331
+ const arrayToElement = new Map<number, number>([
332
+ [OID.boolArray, OID.bool],
333
+ [OID.byteaArray, OID.bytea],
334
+ [OID.nameArray, OID.name],
335
+ [OID.int8Array, OID.int8],
336
+ [OID.int2Array, OID.int2],
337
+ [OID.int4Array, OID.int4],
338
+ [OID.textArray, OID.text],
339
+ [OID.oidArray, OID.oid],
340
+ [OID.jsonArray, OID.json],
341
+ [OID.cidrArray, OID.cidr],
342
+ [OID.float4Array, OID.float4],
343
+ [OID.float8Array, OID.float8],
344
+ [OID.inetArray, OID.inet],
345
+ [OID.bpcharArray, OID.bpchar],
346
+ [OID.varcharArray, OID.varchar],
347
+ [OID.dateArray, OID.date],
348
+ [OID.timeArray, OID.time],
349
+ [OID.timestampArray, OID.timestamp],
350
+ [OID.timestamptzArray, OID.timestamptz],
351
+ [OID.timetzArray, OID.timetz],
352
+ [OID.numericArray, OID.numeric],
353
+ [OID.uuidArray, OID.uuid],
354
+ [OID.jsonbArray, OID.jsonb]
355
+ ])
356
+
357
+ const elementToArray = new Map<number, number>(
358
+ Array.from(arrayToElement, ([array, element]) => [element, array])
359
+ )
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
+
386
+ /**
387
+ * Returns the array OID whose elements have the given OID, or `undefined`
388
+ * when there is no array type registered for it.
389
+ *
390
+ * @category getters
391
+ * @since 4.0.0
392
+ */
393
+ export const arrayOidFor = (elementOid: number, registry?: Registry): number | undefined =>
394
+ registry === undefined ? elementToArray.get(elementOid) : getRegistryState(registry).elementToArray.get(elementOid)
395
+
396
+ // -----------------------------------------------------------------------------
397
+ // calendar helpers
398
+ // -----------------------------------------------------------------------------
399
+
400
+ const pad = (value: number, size: number): string => String(value).padStart(size, "0")
401
+
402
+ /** Civil date from days since 1970-01-01, after Howard Hinnant's algorithm. */
403
+ const civilFromDays = (days: number): { year: number; month: number; day: number } => {
404
+ const z = days + 719468
405
+ const era = Math.floor(z / 146097)
406
+ const doe = z - era * 146097
407
+ const yoe = Math.floor((doe - Math.floor(doe / 1460) + Math.floor(doe / 36524) - Math.floor(doe / 146096)) / 365)
408
+ const y = yoe + era * 400
409
+ const doy = doe - (365 * yoe + Math.floor(yoe / 4) - Math.floor(yoe / 100))
410
+ const mp = Math.floor((5 * doy + 2) / 153)
411
+ const day = doy - Math.floor((153 * mp + 2) / 5) + 1
412
+ const month = mp < 10 ? mp + 3 : mp - 9
413
+ return { year: month <= 2 ? y + 1 : y, month, day }
414
+ }
415
+
416
+ /** Days since 1970-01-01 from a civil date. */
417
+ const daysFromCivil = (year: number, month: number, day: number): number => {
418
+ const y = month <= 2 ? year - 1 : year
419
+ const era = Math.floor(y / 400)
420
+ const yoe = y - era * 400
421
+ const mp = month > 2 ? month - 3 : month + 9
422
+ const doy = Math.floor((153 * mp + 2) / 5) + day - 1
423
+ const doe = yoe * 365 + Math.floor(yoe / 4) - Math.floor(yoe / 100) + doy
424
+ return era * 146097 + doe - 719468
425
+ }
426
+
427
+ const monthLengths = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
428
+
429
+ const isLeapYear = (year: number): boolean => (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0
430
+
431
+ /** The two-digit number at `at`, or `-1` when those characters are not digits. */
432
+ const twoDigitsAt = (text: string, at: number): number => {
433
+ const tens = text.charCodeAt(at) - 48
434
+ const units = text.charCodeAt(at + 1) - 48
435
+ return tens >= 0 && tens <= 9 && units >= 0 && units <= 9 ? tens * 10 + units : -1
436
+ }
437
+
438
+ /** Parses `[-]YYYY-MM-DD`, with as many year digits as the caller likes. */
439
+ const parseDate = (text: string): number => {
440
+ const yearStart = text.charCodeAt(0) === 45 ? 1 : 0
441
+ const yearEnd = text.length - 6
442
+ if (yearEnd - yearStart < 4 || text.charCodeAt(yearEnd) !== 45 || text.charCodeAt(yearEnd + 3) !== 45) {
443
+ return fail(`Expected a YYYY-MM-DD date, received "${text}"`)
444
+ }
445
+ let year = 0
446
+ for (let index = yearStart; index < yearEnd; index++) {
447
+ const digit = text.charCodeAt(index) - 48
448
+ if (digit < 0 || digit > 9) {
449
+ return fail(`Expected a YYYY-MM-DD date, received "${text}"`)
450
+ }
451
+ year = year * 10 + digit
452
+ }
453
+ const month = twoDigitsAt(text, yearEnd + 1)
454
+ const day = twoDigitsAt(text, yearEnd + 4)
455
+ if (month === -1 || day === -1) {
456
+ return fail(`Expected a YYYY-MM-DD date, received "${text}"`)
457
+ }
458
+ if (yearStart === 1) year = -year
459
+ // Enough digits and the year is Infinity, which `daysFromCivil` turns into a
460
+ // NaN the range check below would let through as a zero.
461
+ if (!Number.isFinite(year)) {
462
+ return fail(`date out of range: "${text}"`)
463
+ }
464
+ const monthLength = month === 2 && isLeapYear(year) ? 29 : monthLengths[month]
465
+ if (month < 1 || month > 12 || day < 1 || day > monthLength) {
466
+ return fail(`Invalid date "${text}"`)
467
+ }
468
+ return daysFromCivil(year, month, day)
469
+ }
470
+
471
+ const formatDate = (days: number): string => {
472
+ const { day, month, year } = civilFromDays(days)
473
+ const yearText = year < 0
474
+ ? `-${pad(-year, 4)}`
475
+ : year < 10000
476
+ ? twoDigits[(year / 100) | 0] + twoDigits[year % 100]
477
+ : String(year)
478
+ return `${yearText}-${twoDigits[month]}-${twoDigits[day]}`
479
+ }
480
+
481
+ const timeRegex = /^(\d{1,2}):(\d{2})(?::(\d{2})(?:\.(\d{1,6}))?)?$/
482
+
483
+ /** Microseconds since midnight. A day of them is well inside `Number`'s exact range. */
484
+ const parseTimeOfDay = (text: string): number => {
485
+ const match = timeRegex.exec(text)
486
+ if (match === null) {
487
+ return fail(`Expected a HH:MM[:SS[.ffffff]] time, received "${text}"`)
488
+ }
489
+ const hours = Number(match[1])
490
+ const minutes = Number(match[2])
491
+ const seconds = match[3] === undefined ? 0 : Number(match[3])
492
+ const fraction = match[4] === undefined ? 0 : Number(match[4].padEnd(6, "0"))
493
+ const micros = (hours * 3600 + minutes * 60 + seconds) * 1_000_000 + fraction
494
+ if (hours > 24 || minutes > 59 || seconds > 60 || micros > MAX_TIME_MICROS_NUMBER) {
495
+ return fail(`Invalid time "${text}"`)
496
+ }
497
+ return micros
498
+ }
499
+
500
+ const formatTimeOfDay = (micros: number): string => {
501
+ const seconds = (micros / 1_000_000) | 0
502
+ const fraction = micros - seconds * 1_000_000
503
+ const base = `${twoDigits[(seconds / 3600) | 0]}:${twoDigits[((seconds / 60) | 0) % 60]}:${twoDigits[seconds % 60]}`
504
+ if (fraction === 0) return base
505
+ return `${base}.${pad(fraction, 6).replace(/0+$/, "")}`;
506
+ }
507
+
508
+ const zoneRegex = /^(?:Z|([+-])(\d{2})(?::?(\d{2}))?(?::?(\d{2}))?)$/
509
+
510
+ /** Parses a trailing time zone into seconds east of UTC. */
511
+ const parseZone = (text: string): number => {
512
+ const match = zoneRegex.exec(text)
513
+ if (match === null) {
514
+ return fail(`Expected a time zone offset, received "${text}"`)
515
+ }
516
+ if (match[1] === undefined) return 0
517
+ const minutes = match[3] === undefined ? 0 : Number(match[3])
518
+ const trailingSeconds = match[4] === undefined ? 0 : Number(match[4])
519
+ const seconds = Number(match[2]) * 3600 + minutes * 60 + trailingSeconds
520
+ if (minutes > 59 || trailingSeconds > 59 || seconds >= TZDISP_LIMIT_SECONDS) {
521
+ return fail(`Time zone offset out of range, received "${text}"`)
522
+ }
523
+ return match[1] === "-" ? -seconds : seconds
524
+ }
525
+
526
+ const formatZone = (secondsEast: number): string => {
527
+ const sign = secondsEast < 0 ? "-" : "+"
528
+ const total = Math.abs(secondsEast)
529
+ return `${sign}${pad((total / 3600) | 0, 2)}:${twoDigits[((total / 60) | 0) % 60]}`
530
+ }
531
+
532
+ // -----------------------------------------------------------------------------
533
+ // numeric
534
+ // -----------------------------------------------------------------------------
535
+
536
+ const NUMERIC_POS = 0x0000
537
+ const NUMERIC_NEG = 0x4000
538
+ const NUMERIC_NAN = 0xc000
539
+ const NUMERIC_PINF = 0xd000
540
+ const NUMERIC_NINF = 0xf000
541
+
542
+ const numericRegex = /^([+-])?(\d*)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/
543
+
544
+ const encodeNumeric = (value: unknown): Uint8Array => {
545
+ const text = requireString(value, "numeric")
546
+ if (text === "NaN") return numericSpecial(NUMERIC_NAN)
547
+ if (text === "Infinity") return numericSpecial(NUMERIC_PINF)
548
+ if (text === "-Infinity") return numericSpecial(NUMERIC_NINF)
549
+
550
+ const match = numericRegex.exec(text)
551
+ if (match === null || (match[2] ?? "") + (match[3] ?? "") === "") {
552
+ return fail(`Expected a decimal numeric string, received "${text}"`)
553
+ }
554
+ const exponent = match[4] === undefined ? 0 : Number(match[4])
555
+ let digits = (match[2] ?? "") + (match[3] ?? "")
556
+ let scale = (match[3] ?? "").length - exponent
557
+ if (scale < 0) {
558
+ digits += "0".repeat(-scale)
559
+ scale = 0
560
+ }
561
+ if (scale > 0x3fff) {
562
+ return fail(`numeric scale ${scale} exceeds the PostgreSQL maximum`)
563
+ }
564
+
565
+ const rightPad = (4 - (scale % 4)) % 4
566
+ digits += "0".repeat(rightPad)
567
+ const fractionLength = scale + rightPad
568
+ const leftPad = (4 - ((digits.length - fractionLength) % 4)) % 4
569
+ digits = "0".repeat(leftPad) + digits
570
+
571
+ // Groups are read out of `digits` on demand rather than collected, so
572
+ // trimming the leading and trailing zero groups is two moving indices
573
+ // instead of an array and a `shift` per leading zero.
574
+ const groupAt = (index: number): number => {
575
+ const at = index * 4
576
+ return (digits.charCodeAt(at) - 48) * 1000 + (digits.charCodeAt(at + 1) - 48) * 100 +
577
+ (digits.charCodeAt(at + 2) - 48) * 10 + (digits.charCodeAt(at + 3) - 48)
578
+ }
579
+ const groupCount = digits.length / 4
580
+ let first = 0
581
+ while (first < groupCount && groupAt(first) === 0) first++
582
+ let last = groupCount
583
+ while (last > first && groupAt(last - 1) === 0) last--
584
+ const count = last - first
585
+ const weight = count === 0 ? 0 : (digits.length - fractionLength) / 4 - 1 - first
586
+
587
+ const sign = count > 0 && match[1] === "-" ? NUMERIC_NEG : NUMERIC_POS
588
+ const bytes = new Uint8Array(8 + count * 2)
589
+ writeInt16(bytes, 0, count)
590
+ writeInt16(bytes, 2, weight)
591
+ writeInt16(bytes, 4, sign)
592
+ writeInt16(bytes, 6, scale)
593
+ for (let i = 0; i < count; i++) {
594
+ writeInt16(bytes, 8 + i * 2, groupAt(first + i))
595
+ }
596
+ return bytes
597
+ }
598
+
599
+ const numericSpecial = (sign: number): Uint8Array => {
600
+ const bytes = new Uint8Array(8)
601
+ writeInt16(bytes, 4, sign)
602
+ return bytes
603
+ }
604
+
605
+ const decodeNumeric = (bytes: Uint8Array, offset: number, size: number): string => {
606
+ if (size < 8) return fail("Truncated numeric value")
607
+ const count = readInt16(bytes, offset)
608
+ const weight = readInt16(bytes, offset + 2)
609
+ const sign = readUint16(bytes, offset + 4)
610
+ const scale = readInt16(bytes, offset + 6)
611
+ if (sign === NUMERIC_NAN) return "NaN"
612
+ if (sign === NUMERIC_PINF) return "Infinity"
613
+ if (sign === NUMERIC_NINF) return "-Infinity"
614
+ if (sign !== NUMERIC_POS && sign !== NUMERIC_NEG) {
615
+ return fail(`Invalid numeric sign: ${sign}`)
616
+ }
617
+ requireSize(size, 8 + count * 2, "numeric")
618
+
619
+ const digits = offset + 8
620
+ // Groups outside 0-9999 have no four-digit text, and reading past the ones
621
+ // the value carries is how the fractional part is padded out.
622
+ const digitAt = (index: number): number => {
623
+ if (index < 0 || index >= count) return 0
624
+ const digit = readInt16(bytes, digits + index * 2)
625
+ return digit >= 0 && digit <= 9999 ? digit : fail(`Invalid numeric digit: ${digit}`)
626
+ }
627
+
628
+ let result = sign === NUMERIC_NEG ? "-" : ""
629
+ if (weight < 0) {
630
+ result += "0"
631
+ } else {
632
+ for (let i = 0; i <= weight; i++) {
633
+ const digit = digitAt(i)
634
+ if (i === 0) {
635
+ result += String(digit)
636
+ } else {
637
+ result += twoDigits[(digit / 100) | 0]
638
+ result += twoDigits[digit % 100]
639
+ }
640
+ }
641
+ }
642
+ if (scale > 0) {
643
+ let fraction = ""
644
+ for (let i = weight + 1; fraction.length < scale; i++) {
645
+ const digit = digitAt(i)
646
+ fraction += twoDigits[(digit / 100) | 0]
647
+ fraction += twoDigits[digit % 100]
648
+ }
649
+ result += `.${fraction.slice(0, scale)}`
650
+ }
651
+ return result
652
+ }
653
+
654
+ // -----------------------------------------------------------------------------
655
+ // network addresses
656
+ // -----------------------------------------------------------------------------
657
+
658
+ const PGSQL_AF_INET = 2
659
+ const PGSQL_AF_INET6 = 3
660
+
661
+ const encodeInet = (value: unknown, isCidr: boolean): Uint8Array => {
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
+ }
673
+ }
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
679
+ result[1] = bits
680
+ result[2] = isCidr ? 1 : 0
681
+ result[3] = octets.length
682
+ result.set(octets, 4)
683
+ return result
684
+ }
685
+
686
+ const decodeInet = (bytes: Uint8Array, offset: number, size: number): string => {
687
+ if (size < 4) return fail("Truncated inet value")
688
+ const family = bytes[offset]
689
+ const bits = bytes[offset + 1]
690
+ const cidrFlag = bytes[offset + 2]
691
+ if (cidrFlag > 1) return fail(`Invalid inet CIDR flag: ${cidrFlag}`)
692
+ const isCidr = cidrFlag === 1
693
+ const addressSize = bytes[offset + 3]
694
+ const expected = family === PGSQL_AF_INET ? 4 : family === PGSQL_AF_INET6 ? 16 : -1
695
+ if (expected === -1 || addressSize !== expected) {
696
+ return fail(`Invalid inet address family ${family} with ${addressSize} byte(s)`)
697
+ }
698
+ requireSize(size, 4 + addressSize, "inet")
699
+ if (bits > addressSize * 8) return fail(`Invalid inet netmask length: ${bits}`)
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))
712
+ }
713
+
714
+ // -----------------------------------------------------------------------------
715
+ // uuid
716
+ // -----------------------------------------------------------------------------
717
+
718
+ const hexDigits = "0123456789abcdef"
719
+
720
+ /** Byte value of each hex character code, `-1` for everything else. */
721
+ const hexValues = new Int8Array(128).fill(-1)
722
+ for (let i = 0; i < 16; i++) {
723
+ hexValues[hexDigits.charCodeAt(i)] = i
724
+ hexValues["0123456789ABCDEF".charCodeAt(i)] = i
725
+ }
726
+
727
+ /** Character codes of each byte value's two hex digits. */
728
+ const hexHigh = /* @__PURE__ */ (() => {
729
+ const codes = new Uint8Array(256)
730
+ for (let i = 0; i < 256; i++) codes[i] = hexDigits.charCodeAt(i >> 4)
731
+ return codes
732
+ })()
733
+ const hexLow = /* @__PURE__ */ (() => {
734
+ const codes = new Uint8Array(256)
735
+ for (let i = 0; i < 256; i++) codes[i] = hexDigits.charCodeAt(i & 0xf)
736
+ return codes
737
+ })()
738
+
739
+ /** Offsets of the 16 uuid bytes within the 36-character hyphenated text. */
740
+ const uuidOffsets = [0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34]
741
+
742
+ /** The hex digit at `at`, or `-1` when that character is not one. */
743
+ const hexAt = (text: string, at: number): number => {
744
+ const code = text.charCodeAt(at)
745
+ return code < 128 ? hexValues[code] : -1
746
+ }
747
+
748
+ /**
749
+ * The hyphen positions do the validating that a regular expression used to:
750
+ * anything else in the 36 characters has to be a hex digit for the pairs to
751
+ * come out, and `hexAt` reports the ones that are not.
752
+ */
753
+ /** The four int32 words of the uuid `uuidWords` last parsed. */
754
+ const uuidWord = new Int32Array(4)
755
+
756
+ /**
757
+ * Parses the hyphenated text into `uuidWord`. Both encoding paths read the
758
+ * words from there rather than from a returned array, so neither allocates.
759
+ */
760
+ const uuidWords = (value: unknown): void => {
761
+ const text = requireString(value, "uuid")
762
+ if (
763
+ text.length !== 36 || text.charCodeAt(8) !== 45 || text.charCodeAt(13) !== 45 ||
764
+ text.charCodeAt(18) !== 45 || text.charCodeAt(23) !== 45
765
+ ) {
766
+ return fail(`Expected a UUID, received "${text}"`)
767
+ }
768
+ for (let word = 0; word < 4; word++) {
769
+ let bits = 0
770
+ let valid = 0
771
+ for (let i = word * 4; i < word * 4 + 4; i++) {
772
+ const at = uuidOffsets[i]
773
+ const high = hexAt(text, at)
774
+ const low = hexAt(text, at + 1)
775
+ valid |= high | low
776
+ bits = (bits << 8) | (high << 4) | low
777
+ }
778
+ if (valid < 0) {
779
+ return fail(`Expected a UUID, received "${text}"`)
780
+ }
781
+ uuidWord[word] = bits
782
+ }
783
+ }
784
+
785
+ const encodeUuid = (value: unknown): Uint8Array => {
786
+ uuidWords(value)
787
+ const bytes = new Uint8Array(16)
788
+ writeInt32(bytes, 0, uuidWord[0])
789
+ writeInt32(bytes, 4, uuidWord[1])
790
+ writeInt32(bytes, 8, uuidWord[2])
791
+ writeInt32(bytes, 12, uuidWord[3])
792
+ return bytes
793
+ }
794
+
795
+ // Four int32s are the sixteen bytes, so a uuid parameter needs no array of
796
+ // its own on the way into a frame.
797
+ const writeUuid = (sink: ValueSink, value: unknown): void => {
798
+ uuidWords(value)
799
+ sink.int32(uuidWord[0])
800
+ sink.int32(uuidWord[1])
801
+ sink.int32(uuidWord[2])
802
+ sink.int32(uuidWord[3])
803
+ }
804
+
805
+ // One `String.fromCharCode` call, so the result is a flat string rather than
806
+ // a rope of pair concatenations that its first reader has to flatten. 45 is
807
+ // the hyphen.
808
+ const decodeUuid = (bytes: Uint8Array, offset: number, size: number): string => {
809
+ requireSize(size, 16, "uuid")
810
+ const b0 = bytes[offset]
811
+ const b1 = bytes[offset + 1]
812
+ const b2 = bytes[offset + 2]
813
+ const b3 = bytes[offset + 3]
814
+ const b4 = bytes[offset + 4]
815
+ const b5 = bytes[offset + 5]
816
+ const b6 = bytes[offset + 6]
817
+ const b7 = bytes[offset + 7]
818
+ const b8 = bytes[offset + 8]
819
+ const b9 = bytes[offset + 9]
820
+ const b10 = bytes[offset + 10]
821
+ const b11 = bytes[offset + 11]
822
+ const b12 = bytes[offset + 12]
823
+ const b13 = bytes[offset + 13]
824
+ const b14 = bytes[offset + 14]
825
+ const b15 = bytes[offset + 15]
826
+ return String.fromCharCode(
827
+ hexHigh[b0],
828
+ hexLow[b0],
829
+ hexHigh[b1],
830
+ hexLow[b1],
831
+ hexHigh[b2],
832
+ hexLow[b2],
833
+ hexHigh[b3],
834
+ hexLow[b3],
835
+ 45,
836
+ hexHigh[b4],
837
+ hexLow[b4],
838
+ hexHigh[b5],
839
+ hexLow[b5],
840
+ 45,
841
+ hexHigh[b6],
842
+ hexLow[b6],
843
+ hexHigh[b7],
844
+ hexLow[b7],
845
+ 45,
846
+ hexHigh[b8],
847
+ hexLow[b8],
848
+ hexHigh[b9],
849
+ hexLow[b9],
850
+ 45,
851
+ hexHigh[b10],
852
+ hexLow[b10],
853
+ hexHigh[b11],
854
+ hexLow[b11],
855
+ hexHigh[b12],
856
+ hexLow[b12],
857
+ hexHigh[b13],
858
+ hexLow[b13],
859
+ hexHigh[b14],
860
+ hexLow[b14],
861
+ hexHigh[b15],
862
+ hexLow[b15]
863
+ )
864
+ }
865
+
866
+ // -----------------------------------------------------------------------------
867
+ // codecs
868
+ // -----------------------------------------------------------------------------
869
+
870
+ /**
871
+ * A binary codec for a single OID.
872
+ *
873
+ * @category models
874
+ * @since 4.0.0
875
+ */
876
+ export interface Codec<A> {
877
+ readonly encode: (value: A) => Result.Result<Uint8Array, CodecError>
878
+ readonly decode: (bytes: Uint8Array) => Result.Result<A, CodecError>
879
+ /**
880
+ * Writes the value straight into a `Bind` frame, with no array of its own.
881
+ * Optional: a codec without one falls back to `encode` and a copy.
882
+ */
883
+ readonly write?: (sink: ValueSink, value: A) => Result.Result<void, CodecError>
884
+ /**
885
+ * Reads the value out of the `size` bytes at `offset`, with no view of its
886
+ * own. Optional: a codec without one is handed a view. Array elements are
887
+ * read through this, so it is what keeps decoding an array from allocating
888
+ * a view per element.
889
+ */
890
+ readonly read?: (bytes: Uint8Array, offset: number, size: number) => Result.Result<A, CodecError>
891
+ }
892
+
893
+ interface UnsafeCodec<A> {
894
+ readonly encode: (value: A) => Uint8Array
895
+ readonly decode: (bytes: Uint8Array) => A
896
+ readonly write?: (sink: ValueSink, value: A) => void
897
+ readonly read?: (bytes: Uint8Array, offset: number, size: number) => A
898
+ }
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
+
928
+ /** A codec whose `decode` is its `read` over the whole of its bytes. */
929
+ const codecOf = <A>(
930
+ read: (bytes: Uint8Array, offset: number, size: number) => A,
931
+ encode: (value: A) => Uint8Array,
932
+ write?: (sink: ValueSink, value: A) => void
933
+ ): UnsafeCodec<A> => {
934
+ const decode = (bytes: Uint8Array): A => read(bytes, 0, bytes.length)
935
+ return write === undefined ? { encode, decode, read } : { encode, decode, read, write }
936
+ }
937
+
938
+ /** A fresh copy of the first four staged bytes; `set` costs more than the stores. */
939
+ const takeScratch4 = (): Uint8Array => {
940
+ const bytes = new Uint8Array(4)
941
+ bytes[0] = scratchBytes4[0]
942
+ bytes[1] = scratchBytes4[1]
943
+ bytes[2] = scratchBytes4[2]
944
+ bytes[3] = scratchBytes4[3]
945
+ return bytes
946
+ }
947
+
948
+ const takeScratch8 = (): Uint8Array => {
949
+ const bytes = new Uint8Array(8)
950
+ bytes[0] = scratchBytes8[0]
951
+ bytes[1] = scratchBytes8[1]
952
+ bytes[2] = scratchBytes8[2]
953
+ bytes[3] = scratchBytes8[3]
954
+ bytes[4] = scratchBytes8[4]
955
+ bytes[5] = scratchBytes8[5]
956
+ bytes[6] = scratchBytes8[6]
957
+ bytes[7] = scratchBytes8[7]
958
+ return bytes
959
+ }
960
+
961
+ const utf8Codec: UnsafeCodec<any> = codecOf(
962
+ decodeUtf8,
963
+ (value) => encodeUtf8(requireString(value, "text")),
964
+ (sink, value) => sink.utf8(requireString(value, "text"))
965
+ )
966
+
967
+ const int8Value = (value: unknown): bigint => {
968
+ const big = requireBigInt(value, "int8")
969
+ if (big < INT64_MIN || big > INT64_MAX) fail(`int8 out of range: ${big}`)
970
+ return big
971
+ }
972
+
973
+ const MAX_TIME_MICROS = BigInt(MAX_TIME_MICROS_NUMBER)
974
+
975
+ const timeValue = (value: unknown): bigint => {
976
+ const micros = requireBigInt(value, "time")
977
+ if (micros < ZERO || micros > MAX_TIME_MICROS) fail(`time out of range: ${micros}`)
978
+ return micros
979
+ }
980
+
981
+ /**
982
+ * Microseconds since midnight, which always fit a `Number` exactly. Formatting
983
+ * one assumes that, so a value the type cannot hold is an error rather than a
984
+ * nonsense time.
985
+ */
986
+ const readTimeMicros = (bytes: Uint8Array, offset: number): number => {
987
+ const high = readInt32(bytes, offset)
988
+ const micros = high * 4294967296 + readUint32(bytes, offset + 4)
989
+ if (high < 0 || micros > MAX_TIME_MICROS_NUMBER) {
990
+ return fail(`timetz out of range: ${micros}`)
991
+ }
992
+ return micros
993
+ }
994
+
995
+ /** The two halves of the int64 `timestampInt64` last produced. */
996
+ let timestampHigh = 0
997
+ let timestampLow = 0
998
+
999
+ /**
1000
+ * Converts epoch milliseconds to the halves of the wire int64. Both encoding
1001
+ * paths read them from here rather than from a returned pair, so neither
1002
+ * allocates.
1003
+ */
1004
+ const timestampInt64 = (value: unknown): void => {
1005
+ const ms = requireNumber(value, "timestamp")
1006
+ if (Number.isNaN(ms)) {
1007
+ fail("timestamp cannot be NaN")
1008
+ } else if (ms === Number.POSITIVE_INFINITY) {
1009
+ timestampHigh = INT32_MAX
1010
+ timestampLow = -1
1011
+ } else if (ms === Number.NEGATIVE_INFINITY) {
1012
+ timestampHigh = INT32_MIN
1013
+ timestampLow = 0
1014
+ } else {
1015
+ const unixMicros = Math.trunc(ms * 1000)
1016
+ if (!Number.isFinite(unixMicros)) {
1017
+ fail(`timestamp out of range: ${ms}`)
1018
+ }
1019
+ const micros = unixMicros - PG_EPOCH_MICROS
1020
+ if (micros > -MAX_EXACT && micros < MAX_EXACT) {
1021
+ timestampHigh = Math.floor(micros / 4294967296)
1022
+ timestampLow = micros - timestampHigh * 4294967296
1023
+ } else {
1024
+ const exact = BigInt(unixMicros) - BigInt(PG_EPOCH_MICROS)
1025
+ if (exact < INT64_MIN || exact > INT64_MAX) {
1026
+ fail(`timestamp out of range: ${ms}`)
1027
+ }
1028
+ scratchView8.setBigInt64(0, exact)
1029
+ timestampHigh = scratchView8.getInt32(0)
1030
+ timestampLow = scratchView8.getInt32(4)
1031
+ }
1032
+ }
1033
+ }
1034
+
1035
+ const timestampCodec: UnsafeCodec<any> = codecOf(
1036
+ (bytes, offset, size) => {
1037
+ requireSize(size, 8, "timestamp")
1038
+ const high = readInt32(bytes, offset)
1039
+ if (high >= -MAX_EXACT_HIGH && high < MAX_EXACT_HIGH) {
1040
+ // Inside these bounds the whole conversion is float arithmetic, so it
1041
+ // allocates no BigInt. Everything outside them, the sentinels included,
1042
+ // needs the exact 64-bit value.
1043
+ const micros = high * 4294967296 + readUint32(bytes, offset + 4)
1044
+ return (micros - micros % 1000) / 1000 + PG_EPOCH_MS
1045
+ }
1046
+ stage8(bytes, offset)
1047
+ const micros = scratchView8.getBigInt64(0)
1048
+ if (micros === INT64_MAX) return Number.POSITIVE_INFINITY
1049
+ if (micros === INT64_MIN) return Number.NEGATIVE_INFINITY
1050
+ return Number(micros / THOUSAND) + PG_EPOCH_MS
1051
+ },
1052
+ (value) => {
1053
+ timestampInt64(value)
1054
+ const bytes = new Uint8Array(8)
1055
+ writeInt32(bytes, 0, timestampHigh)
1056
+ writeInt32(bytes, 4, timestampLow)
1057
+ return bytes
1058
+ },
1059
+ // Two int32s are the int64, so the sink needs nothing of its own for it.
1060
+ (sink, value) => {
1061
+ timestampInt64(value)
1062
+ sink.int32(timestampHigh)
1063
+ sink.int32(timestampLow)
1064
+ }
1065
+ )
1066
+
1067
+ /** Days since the PostgreSQL epoch, or an infinity sentinel. */
1068
+ const dateDays = (value: unknown): number => {
1069
+ const text = requireString(value, "date")
1070
+ if (text === "infinity") return INT32_MAX
1071
+ if (text === "-infinity") return INT32_MIN
1072
+ const days = parseDate(text) - PG_EPOCH_DAYS
1073
+ if (days <= INT32_MIN || days >= INT32_MAX) {
1074
+ return fail(`date out of range: "${text}"`)
1075
+ }
1076
+ return days
1077
+ }
1078
+
1079
+ const dateCodec: UnsafeCodec<any> = codecOf(
1080
+ (bytes, offset, size) => {
1081
+ requireSize(size, 4, "date")
1082
+ const days = readInt32(bytes, offset)
1083
+ if (days === INT32_MAX) return "infinity"
1084
+ if (days === INT32_MIN) return "-infinity"
1085
+ return formatDate(days + PG_EPOCH_DAYS)
1086
+ },
1087
+ (value) => {
1088
+ const bytes = new Uint8Array(4)
1089
+ writeInt32(bytes, 0, dateDays(value))
1090
+ return bytes
1091
+ },
1092
+ (sink, value) => sink.int32(dateDays(value))
1093
+ )
1094
+
1095
+ const timetzCodec: UnsafeCodec<any> = codecOf(
1096
+ (bytes, offset, size) => {
1097
+ requireSize(size, 12, "timetz")
1098
+ const zone = readInt32(bytes, offset + 8)
1099
+ if (zone <= -TZDISP_LIMIT_SECONDS || zone >= TZDISP_LIMIT_SECONDS) {
1100
+ return fail(`timetz time zone displacement out of range: ${zone}`)
1101
+ }
1102
+ return formatTimeOfDay(readTimeMicros(bytes, offset)) + formatZone(-zone)
1103
+ },
1104
+ (value) => {
1105
+ const text = requireString(value, "timetz")
1106
+ const split = Math.max(text.lastIndexOf("+"), text.lastIndexOf("-"), text.lastIndexOf("Z"))
1107
+ if (split <= 0) {
1108
+ return fail(`Expected a timetz with a zone offset, received "${text}"`)
1109
+ }
1110
+ const bytes = new Uint8Array(12)
1111
+ writeInt64(bytes, 0, parseTimeOfDay(text.slice(0, split)))
1112
+ writeInt32(bytes, 8, -parseZone(text.slice(split)))
1113
+ return bytes
1114
+ }
1115
+ )
1116
+
1117
+ const jsonCodec: UnsafeCodec<any> = codecOf(
1118
+ (bytes, offset, size) => JSON.parse(decodeUtf8(bytes, offset, size)),
1119
+ (value) => {
1120
+ const text = JSON.stringify(value)
1121
+ if (text === undefined) return fail("Value cannot be serialised as JSON")
1122
+ return encodeUtf8(text)
1123
+ },
1124
+ (sink, value) => {
1125
+ const text = JSON.stringify(value)
1126
+ if (text === undefined) return fail("Value cannot be serialised as JSON")
1127
+ sink.utf8(text)
1128
+ }
1129
+ )
1130
+
1131
+ const jsonbCodec: UnsafeCodec<any> = codecOf(
1132
+ (bytes, offset, size) => {
1133
+ if (size === 0 || bytes[offset] !== 1) {
1134
+ return fail("Unsupported jsonb version byte")
1135
+ }
1136
+ return JSON.parse(decodeUtf8(bytes, offset + 1, size - 1))
1137
+ },
1138
+ (value) => {
1139
+ const text = JSON.stringify(value)
1140
+ if (text === undefined) return fail("Value cannot be serialised as JSON")
1141
+ const bytes = encodeUtf8Prefixed(text, 1)
1142
+ bytes[0] = 1
1143
+ return bytes
1144
+ },
1145
+ (sink, value) => {
1146
+ const text = JSON.stringify(value)
1147
+ if (text === undefined) return fail("Value cannot be serialised as JSON")
1148
+ sink.uint8(1)
1149
+ sink.utf8(text)
1150
+ }
1151
+ )
1152
+
1153
+ const builtinScalars = new Map<number, UnsafeCodec<any>>([
1154
+ [
1155
+ OID.bool,
1156
+ codecOf(
1157
+ (bytes, offset, size) => {
1158
+ requireSize(size, 1, "bool")
1159
+ return bytes[offset] !== 0
1160
+ },
1161
+ (value) => {
1162
+ if (typeof value !== "boolean") fail("Expected a boolean for bool")
1163
+ const bytes = new Uint8Array(1)
1164
+ bytes[0] = value ? 1 : 0
1165
+ return bytes
1166
+ },
1167
+ (sink, value) => {
1168
+ if (typeof value !== "boolean") fail("Expected a boolean for bool")
1169
+ sink.uint8(value ? 1 : 0)
1170
+ }
1171
+ )
1172
+ ],
1173
+ [
1174
+ OID.bytea,
1175
+ codecOf(
1176
+ region,
1177
+ (value) => value instanceof Uint8Array ? value.slice() : fail("Expected a Uint8Array for bytea"),
1178
+ (sink, value) => value instanceof Uint8Array ? sink.raw(value) : fail("Expected a Uint8Array for bytea")
1179
+ )
1180
+ ],
1181
+ [
1182
+ OID.int2,
1183
+ codecOf(
1184
+ (bytes, offset, size) => {
1185
+ requireSize(size, 2, "int2")
1186
+ return ((bytes[offset] << 8) | bytes[offset + 1]) << 16 >> 16
1187
+ },
1188
+ (value) => {
1189
+ const num = requireInteger(value, "int2", -32768, 32767)
1190
+ const bytes = new Uint8Array(2)
1191
+ bytes[0] = num >>> 8
1192
+ bytes[1] = num
1193
+ return bytes
1194
+ },
1195
+ (sink, value) => sink.int16(requireInteger(value, "int2", -32768, 32767))
1196
+ )
1197
+ ],
1198
+ [
1199
+ OID.int4,
1200
+ codecOf(
1201
+ (bytes, offset, size) => {
1202
+ requireSize(size, 4, "int4")
1203
+ return readInt32(bytes, offset)
1204
+ },
1205
+ (value) => {
1206
+ const num = requireInteger(value, "int4", INT32_MIN, INT32_MAX)
1207
+ const bytes = new Uint8Array(4)
1208
+ bytes[0] = num >>> 24
1209
+ bytes[1] = num >>> 16
1210
+ bytes[2] = num >>> 8
1211
+ bytes[3] = num
1212
+ return bytes
1213
+ },
1214
+ (sink, value) => sink.int32(requireInteger(value, "int4", INT32_MIN, INT32_MAX))
1215
+ )
1216
+ ],
1217
+ [
1218
+ OID.oid,
1219
+ codecOf(
1220
+ (bytes, offset, size) => {
1221
+ requireSize(size, 4, "oid")
1222
+ return readUint32(bytes, offset)
1223
+ },
1224
+ (value) => {
1225
+ const num = requireInteger(value, "oid", 0, 4294967295)
1226
+ const bytes = new Uint8Array(4)
1227
+ bytes[0] = num >>> 24
1228
+ bytes[1] = num >>> 16
1229
+ bytes[2] = num >>> 8
1230
+ bytes[3] = num
1231
+ return bytes
1232
+ },
1233
+ (sink, value) => sink.int32(requireInteger(value, "oid", 0, 4294967295))
1234
+ )
1235
+ ],
1236
+ [
1237
+ OID.int8,
1238
+ codecOf(
1239
+ (bytes, offset, size) => {
1240
+ requireSize(size, 8, "int8")
1241
+ stage8(bytes, offset)
1242
+ return scratchView8.getBigInt64(0)
1243
+ },
1244
+ (value) => {
1245
+ scratchView8.setBigInt64(0, int8Value(value))
1246
+ return takeScratch8()
1247
+ },
1248
+ (sink, value) => sink.bigInt64(int8Value(value))
1249
+ )
1250
+ ],
1251
+ [
1252
+ OID.float4,
1253
+ codecOf(
1254
+ (bytes, offset, size) => {
1255
+ requireSize(size, 4, "float4")
1256
+ scratchView4.setInt32(0, readInt32(bytes, offset))
1257
+ return scratchView4.getFloat32(0)
1258
+ },
1259
+ (value) => {
1260
+ scratchView4.setFloat32(0, requireNumber(value, "float4"))
1261
+ return takeScratch4()
1262
+ },
1263
+ (sink, value) => sink.float32(requireNumber(value, "float4"))
1264
+ )
1265
+ ],
1266
+ [
1267
+ OID.float8,
1268
+ codecOf(
1269
+ (bytes, offset, size) => {
1270
+ requireSize(size, 8, "float8")
1271
+ stage8(bytes, offset)
1272
+ return scratchView8.getFloat64(0)
1273
+ },
1274
+ (value) => {
1275
+ scratchView8.setFloat64(0, requireNumber(value, "float8"))
1276
+ return takeScratch8()
1277
+ },
1278
+ (sink, value) => sink.float64(requireNumber(value, "float8"))
1279
+ )
1280
+ ],
1281
+ [
1282
+ OID.time,
1283
+ codecOf(
1284
+ (bytes, offset, size) => {
1285
+ requireSize(size, 8, "time")
1286
+ stage8(bytes, offset)
1287
+ return scratchView8.getBigInt64(0)
1288
+ },
1289
+ (value) => {
1290
+ scratchView8.setBigInt64(0, timeValue(value))
1291
+ return takeScratch8()
1292
+ },
1293
+ (sink, value) => sink.bigInt64(timeValue(value))
1294
+ )
1295
+ ],
1296
+ [OID.numeric, codecOf(decodeNumeric, encodeNumeric)],
1297
+ [OID.text, utf8Codec],
1298
+ [OID.varchar, utf8Codec],
1299
+ [OID.bpchar, utf8Codec],
1300
+ [OID.name, utf8Codec],
1301
+ [OID.json, jsonCodec],
1302
+ [OID.jsonb, jsonbCodec],
1303
+ [OID.uuid, codecOf(decodeUuid, encodeUuid, writeUuid)],
1304
+ [OID.inet, codecOf(decodeInet, (value) => encodeInet(value, false))],
1305
+ [OID.cidr, codecOf(decodeInet, (value) => encodeInet(value, true))],
1306
+ [OID.date, dateCodec],
1307
+ [OID.timetz, timetzCodec],
1308
+ [OID.timestamp, timestampCodec],
1309
+ [OID.timestamptz, timestampCodec]
1310
+ ])
1311
+
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)
1317
+ )
1318
+
1319
+ const builtins = new Map<number, UnsafeCodec<any>>(builtinScalars)
1320
+
1321
+ /**
1322
+ * Built-ins with registered codecs layered over them, so `encode` and `decode`
1323
+ * resolve an OID with a single lookup.
1324
+ */
1325
+ const codecs = new Map<number, UnsafeCodec<any>>(builtins)
1326
+
1327
+ /**
1328
+ * Every built-in OID is below this, and PostgreSQL hands user-defined types
1329
+ * OIDs from 16384 up, so a direct table covers the common case and the map
1330
+ * covers registered ones.
1331
+ */
1332
+ const tableSize = 4096
1333
+
1334
+ const table = new Array<UnsafeCodec<any> | undefined>(tableSize)
1335
+
1336
+ const lookup = (oid: number): UnsafeCodec<any> | undefined => oid >= 0 && oid < tableSize ? table[oid] : codecs.get(oid)
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
+
1398
+ /**
1399
+ * Registers a binary codec for an OID the built-in catalogue does not cover,
1400
+ * or overrides a built-in one. Registered codecs take precedence.
1401
+ *
1402
+ * @category registry
1403
+ * @since 4.0.0
1404
+ */
1405
+ export const register = <A>(oid: number, codec: Codec<A>): void => {
1406
+ const unsafe = toUnsafeCodec(codec)
1407
+ codecs.set(oid, unsafe)
1408
+ if (oid >= 0 && oid < tableSize) table[oid] = unsafe
1409
+ }
1410
+
1411
+ /**
1412
+ * Removes a previously registered codec.
1413
+ *
1414
+ * @category registry
1415
+ * @since 4.0.0
1416
+ */
1417
+ export const unregister = (oid: number): void => {
1418
+ const builtin = builtins.get(oid)
1419
+ if (builtin === undefined) codecs.delete(oid)
1420
+ else codecs.set(oid, builtin)
1421
+ if (oid >= 0 && oid < tableSize) table[oid] = builtin
1422
+ }
1423
+
1424
+ // -----------------------------------------------------------------------------
1425
+ // arrays
1426
+ // -----------------------------------------------------------------------------
1427
+
1428
+ const encodeArray = (value: unknown, elementOid: number, lookup: Lookup): Uint8Array => {
1429
+ if (!Array.isArray(value)) {
1430
+ return fail("Expected an array")
1431
+ }
1432
+ const codec = lookup(elementOid)
1433
+ if (codec === undefined) return fail(`No codec registered for OID ${elementOid}`)
1434
+ const count = value.length
1435
+ const elements: Array<Uint8Array | null> = new Array(count)
1436
+ let hasNull = false
1437
+ let payloadSize = 0
1438
+ for (let i = 0; i < count; i++) {
1439
+ const element = value[i]
1440
+ if (element === null) {
1441
+ elements[i] = null
1442
+ hasNull = true
1443
+ payloadSize += 4
1444
+ } else {
1445
+ const encoded = codec.encode(element)
1446
+ elements[i] = encoded
1447
+ payloadSize += 4 + encoded.length
1448
+ }
1449
+ }
1450
+ const dimensions = count === 0 ? 0 : 1
1451
+ const bytes = new Uint8Array(12 + dimensions * 8 + payloadSize)
1452
+ writeInt32(bytes, 0, dimensions)
1453
+ writeInt32(bytes, 4, hasNull ? 1 : 0)
1454
+ writeInt32(bytes, 8, elementOid)
1455
+ let offset = 12
1456
+ if (dimensions === 1) {
1457
+ writeInt32(bytes, offset, count)
1458
+ writeInt32(bytes, offset + 4, 1)
1459
+ offset += 8
1460
+ }
1461
+ for (let i = 0; i < count; i++) {
1462
+ const element = elements[i]
1463
+ if (element === null) {
1464
+ writeInt32(bytes, offset, -1)
1465
+ offset += 4
1466
+ } else {
1467
+ writeInt32(bytes, offset, element.length)
1468
+ bytes.set(element, offset + 4)
1469
+ offset += 4 + element.length
1470
+ }
1471
+ }
1472
+ return bytes
1473
+ }
1474
+
1475
+ /**
1476
+ * As `encodeArray`, but into a sink: each element is framed with `beginLength`
1477
+ * and written in place, so neither the elements nor the array itself needs an
1478
+ * array of bytes of its own.
1479
+ */
1480
+ const writeArray = (sink: ValueSink, value: unknown, elementOid: number, lookup: Lookup): void => {
1481
+ if (!Array.isArray(value)) {
1482
+ return fail("Expected an array")
1483
+ }
1484
+ const count = value.length
1485
+ // The null flag sits ahead of the elements, so it is the one thing that has
1486
+ // to be known before any of them are written.
1487
+ let hasNull = false
1488
+ for (let i = 0; i < count; i++) {
1489
+ if (value[i] === null) {
1490
+ hasNull = true
1491
+ break
1492
+ }
1493
+ }
1494
+ const dimensions = count === 0 ? 0 : 1
1495
+ sink.int32(dimensions)
1496
+ sink.int32(hasNull ? 1 : 0)
1497
+ sink.int32(elementOid)
1498
+ if (dimensions === 1) {
1499
+ sink.int32(count)
1500
+ sink.int32(1)
1501
+ }
1502
+ // One lookup for the whole array. A codec with no writer of its own is left
1503
+ // to `writeValue`, which allocates for the element anyway.
1504
+ const write = lookup(elementOid)?.write
1505
+ for (let i = 0; i < count; i++) {
1506
+ const element = value[i]
1507
+ if (element === null) {
1508
+ sink.int32(-1)
1509
+ } else {
1510
+ const token = sink.beginLength()
1511
+ if (write === undefined) writeValue(sink, element, elementOid, lookup)
1512
+ else write(sink, element)
1513
+ sink.endLength(token)
1514
+ }
1515
+ }
1516
+ }
1517
+
1518
+ /**
1519
+ * The element codec is resolved once for the whole array, and read in place
1520
+ * where it can be, so a thousand elements cost a thousand reads rather than a
1521
+ * thousand lookups and a thousand views.
1522
+ */
1523
+ const decodeArray = (
1524
+ bytes: Uint8Array,
1525
+ start: number,
1526
+ size: number,
1527
+ elementOid: number,
1528
+ lookup: Lookup
1529
+ ): ReadonlyArray<unknown> => {
1530
+ if (size < 12) return fail("Truncated array value")
1531
+ const dimensions = readInt32(bytes, start)
1532
+ const wireElementOid = readUint32(bytes, start + 8)
1533
+ if (wireElementOid !== elementOid) {
1534
+ return fail(`Array element OID ${wireElementOid} does not match expected OID ${elementOid}`)
1535
+ }
1536
+ if (dimensions === 0) {
1537
+ if (size !== 12) return fail("Zero-dimensional array has trailing bytes")
1538
+ return []
1539
+ }
1540
+ if (dimensions !== 1) {
1541
+ return fail(`Only 1-dimensional arrays are supported, received ${dimensions} dimensions`)
1542
+ }
1543
+ if (size < 20) return fail("Truncated array value")
1544
+ const length = readInt32(bytes, start + 12)
1545
+ if (length < 0) return fail(`Invalid array length: ${length}`)
1546
+ const lowerBound = readInt32(bytes, start + 16)
1547
+ if (lowerBound !== 1) return fail(`Only arrays with a lower bound of 1 are supported, received ${lowerBound}`)
1548
+ const codec = lookup(elementOid)
1549
+ const read = codec?.read
1550
+ const values: Array<unknown> = new Array(length)
1551
+ const limit = start + size
1552
+ let offset = start + 20
1553
+ for (let i = 0; i < length; i++) {
1554
+ if (offset + 4 > limit) return fail("Truncated array element")
1555
+ const elementSize = readInt32(bytes, offset)
1556
+ offset += 4
1557
+ if (elementSize < -1) return fail(`Invalid array element length: ${elementSize}`)
1558
+ if (elementSize === -1) {
1559
+ values[i] = null
1560
+ } else {
1561
+ if (offset + elementSize > limit) return fail("Truncated array element")
1562
+ values[i] = read !== undefined
1563
+ ? read(bytes, offset, elementSize)
1564
+ : codec === undefined
1565
+ ? region(bytes, offset, elementSize)
1566
+ : codec.decode(region(bytes, offset, elementSize))
1567
+ offset += elementSize
1568
+ }
1569
+ }
1570
+ if (offset !== limit) return fail("Array value has trailing bytes")
1571
+ return values
1572
+ }
1573
+
1574
+ // -----------------------------------------------------------------------------
1575
+ // entry points
1576
+ // -----------------------------------------------------------------------------
1577
+
1578
+ /**
1579
+ * A result column, as `RowDescription` describes one.
1580
+ *
1581
+ * @category models
1582
+ * @since 4.0.0
1583
+ */
1584
+ export interface Column {
1585
+ readonly dataTypeOid: number
1586
+ readonly format: number
1587
+ }
1588
+
1589
+ /**
1590
+ * Creates a field reader for `PgProtocol.makeParser`.
1591
+ *
1592
+ * **Details**
1593
+ *
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`.
1597
+ *
1598
+ * **Example** (Updating the reader after `RowDescription`)
1599
+ *
1600
+ * ```ts
1601
+ * import { PgProtocol, PgTypes } from "@effect/sql-pg"
1602
+ *
1603
+ * const parser = PgProtocol.makeParser({ readField: Result.getOrThrow(PgTypes.makeFieldReader([])) })
1604
+ * // on each RowDescription
1605
+ * declare const description: PgProtocol.RowDescription
1606
+ * parser.readField = Result.getOrThrow(PgTypes.makeFieldReader(description.fields))
1607
+ * ```
1608
+ *
1609
+ * @category decoding
1610
+ * @since 4.0.0
1611
+ */
1612
+ export const makeFieldReader = (
1613
+ columns: ReadonlyArray<Column>,
1614
+ registry?: Registry
1615
+ ): Result.Result<PgProtocol.FieldReader<unknown>, CodecError> =>
1616
+ result(() => {
1617
+ const lookup = lookupFor(registry)
1618
+ const codecs = columns.map((column, index) => {
1619
+ if (column.format !== 1) {
1620
+ return fail(`Only the binary format is supported, column ${index} has format ${column.format}`)
1621
+ }
1622
+ return lookup(column.dataTypeOid)
1623
+ })
1624
+ return (bytes: Uint8Array, offset: number, size: number, column: number): unknown => {
1625
+ if (size < 0) return null
1626
+ const codec = codecs[column]
1627
+ if (codec === undefined) return bytes.slice(offset, offset + size)
1628
+ const read = codec.read
1629
+ return read === undefined ? codec.decode(bytes.subarray(offset, offset + size)) : read(bytes, offset, size)
1630
+ }
1631
+ })
1632
+
1633
+ /**
1634
+ * Encodes a JavaScript value as the binary representation of the given OID.
1635
+ *
1636
+ * **Details**
1637
+ *
1638
+ * Returns a `CodecError` failure when the value has the wrong JavaScript type,
1639
+ * or when the OID is neither built in nor registered.
1640
+ *
1641
+ * @category encoding
1642
+ * @since 4.0.0
1643
+ */
1644
+ export const encode = (value: unknown, oid: number, registry?: Registry): Result.Result<Uint8Array, CodecError> =>
1645
+ result(() => {
1646
+ const codec = lookupFor(registry)(oid)
1647
+ if (codec === undefined) return fail(`No codec registered for OID ${oid}`)
1648
+ return codec.encode(value)
1649
+ })
1650
+
1651
+ /**
1652
+ * Decodes the binary representation of the given OID.
1653
+ *
1654
+ * **Details**
1655
+ *
1656
+ * `format` must be `1`; the text format is not implemented. An OID that is
1657
+ * neither built in nor registered decodes to the raw bytes.
1658
+ *
1659
+ * @category decoding
1660
+ * @since 4.0.0
1661
+ */
1662
+ export const decode = (
1663
+ bytes: Uint8Array,
1664
+ oid: number,
1665
+ format: number,
1666
+ registry?: Registry
1667
+ ): Result.Result<unknown, CodecError> =>
1668
+ result(() => {
1669
+ if (format !== 1) {
1670
+ return fail(`Only the binary format is supported, received format ${format}`)
1671
+ }
1672
+ const codec = lookupFor(registry)(oid)
1673
+ return codec === undefined ? bytes : codec.decode(bytes)
1674
+ })
1675
+
1676
+ // -----------------------------------------------------------------------------
1677
+ // parameter constructors
1678
+ // -----------------------------------------------------------------------------
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
+
1696
+ /**
1697
+ * A value paired with the OID it should be encoded as.
1698
+ *
1699
+ * @category models
1700
+ * @since 4.0.0
1701
+ */
1702
+ export interface Parameter {
1703
+ readonly [ParameterTypeId]: ParameterTypeId
1704
+ readonly oid: number
1705
+ readonly value: unknown
1706
+ }
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
+
1717
+ /**
1718
+ * Encodes a parameter for a `Bind` message. SQL NULL stays `null`.
1719
+ *
1720
+ * @category encoding
1721
+ * @since 4.0.0
1722
+ */
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)
1728
+
1729
+ const writeValue = (sink: ValueSink, value: unknown, oid: number, lookup: Lookup): void => {
1730
+ const codec = lookup(oid)
1731
+ if (codec === undefined) return fail(`No codec registered for OID ${oid}`)
1732
+ if (codec.write === undefined) sink.raw(codec.encode(value))
1733
+ else codec.write(sink, value)
1734
+ }
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
+
1746
+ /**
1747
+ * Writes a parameter into a `Bind` frame, for `PgProtocol.makeBindEncoder`.
1748
+ * Codecs that can write their bytes in place do; the rest fall back to
1749
+ * `encode` and a copy.
1750
+ *
1751
+ * @category encoding
1752
+ * @since 4.0.0
1753
+ */
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)
1762
+
1763
+ /**
1764
+ * Writes a parameter into a `Bind` frame.
1765
+ *
1766
+ * @category encoding
1767
+ * @since 4.0.0
1768
+ */
1769
+ export const writeParameter = (
1770
+ sink: ValueSink,
1771
+ parameter: Parameter,
1772
+ registry?: Registry
1773
+ ): Result.Result<void, CodecError> => {
1774
+ try {
1775
+ writeParameterUnsafe(sink, parameter, lookupFor(registry))
1776
+ return Result.void
1777
+ } catch (error) {
1778
+ if (error instanceof CodecError) return Result.fail(error)
1779
+ throw error
1780
+ }
1781
+ }
1782
+
1783
+ const valueWriterUnsafe = Symbol.for("@effect/sql-pg/PgProtocol/ValueWriter/unsafe")
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
+ })
1793
+
1794
+ const parameter = (oid: number) => (value: unknown): Parameter => makeParameter(oid, value)
1795
+
1796
+ /**
1797
+ * A `bool` parameter.
1798
+ *
1799
+ * @category constructors
1800
+ * @since 4.0.0
1801
+ */
1802
+ export const bool: (value: boolean | null) => Parameter = parameter(OID.bool)
1803
+
1804
+ /**
1805
+ * An `int2` parameter.
1806
+ *
1807
+ * @category constructors
1808
+ * @since 4.0.0
1809
+ */
1810
+ export const int2: (value: number | null) => Parameter = parameter(OID.int2)
1811
+
1812
+ /**
1813
+ * An `int4` parameter.
1814
+ *
1815
+ * @category constructors
1816
+ * @since 4.0.0
1817
+ */
1818
+ export const int4: (value: number | null) => Parameter = parameter(OID.int4)
1819
+
1820
+ /**
1821
+ * An `int8` parameter.
1822
+ *
1823
+ * @category constructors
1824
+ * @since 4.0.0
1825
+ */
1826
+ export const int8: (value: bigint | null) => Parameter = parameter(OID.int8)
1827
+
1828
+ /**
1829
+ * An `oid` parameter.
1830
+ *
1831
+ * @category constructors
1832
+ * @since 4.0.0
1833
+ */
1834
+ export const oid: (value: number | null) => Parameter = parameter(OID.oid)
1835
+
1836
+ /**
1837
+ * A `float4` parameter.
1838
+ *
1839
+ * @category constructors
1840
+ * @since 4.0.0
1841
+ */
1842
+ export const float4: (value: number | null) => Parameter = parameter(OID.float4)
1843
+
1844
+ /**
1845
+ * A `float8` parameter.
1846
+ *
1847
+ * @category constructors
1848
+ * @since 4.0.0
1849
+ */
1850
+ export const float8: (value: number | null) => Parameter = parameter(OID.float8)
1851
+
1852
+ /**
1853
+ * A `numeric` parameter, given as a decimal string or `"NaN"`.
1854
+ *
1855
+ * @category constructors
1856
+ * @since 4.0.0
1857
+ */
1858
+ export const numeric: (value: string | null) => Parameter = parameter(OID.numeric)
1859
+
1860
+ /**
1861
+ * A `text` parameter.
1862
+ *
1863
+ * @category constructors
1864
+ * @since 4.0.0
1865
+ */
1866
+ export const text: (value: string | null) => Parameter = parameter(OID.text)
1867
+
1868
+ /**
1869
+ * A `varchar` parameter.
1870
+ *
1871
+ * @category constructors
1872
+ * @since 4.0.0
1873
+ */
1874
+ export const varchar: (value: string | null) => Parameter = parameter(OID.varchar)
1875
+
1876
+ /**
1877
+ * A `bpchar` parameter.
1878
+ *
1879
+ * @category constructors
1880
+ * @since 4.0.0
1881
+ */
1882
+ export const bpchar: (value: string | null) => Parameter = parameter(OID.bpchar)
1883
+
1884
+ /**
1885
+ * A `name` parameter.
1886
+ *
1887
+ * @category constructors
1888
+ * @since 4.0.0
1889
+ */
1890
+ export const name: (value: string | null) => Parameter = parameter(OID.name)
1891
+
1892
+ /**
1893
+ * A `bytea` parameter.
1894
+ *
1895
+ * @category constructors
1896
+ * @since 4.0.0
1897
+ */
1898
+ export const bytea: (value: Uint8Array | null) => Parameter = parameter(OID.bytea)
1899
+
1900
+ /**
1901
+ * A `json` parameter.
1902
+ *
1903
+ * @category constructors
1904
+ * @since 4.0.0
1905
+ */
1906
+ export const json: (value: unknown) => Parameter = parameter(OID.json)
1907
+
1908
+ /**
1909
+ * A `jsonb` parameter.
1910
+ *
1911
+ * @category constructors
1912
+ * @since 4.0.0
1913
+ */
1914
+ export const jsonb: (value: unknown) => Parameter = parameter(OID.jsonb)
1915
+
1916
+ /**
1917
+ * A `uuid` parameter.
1918
+ *
1919
+ * @category constructors
1920
+ * @since 4.0.0
1921
+ */
1922
+ export const uuid: (value: string | null) => Parameter = parameter(OID.uuid)
1923
+
1924
+ /**
1925
+ * An `inet` parameter, such as `"10.0.0.1"` or `"10.0.0.0/8"`.
1926
+ *
1927
+ * @category constructors
1928
+ * @since 4.0.0
1929
+ */
1930
+ export const inet: (value: string | null) => Parameter = parameter(OID.inet)
1931
+
1932
+ /**
1933
+ * A `cidr` parameter.
1934
+ *
1935
+ * @category constructors
1936
+ * @since 4.0.0
1937
+ */
1938
+ export const cidr: (value: string | null) => Parameter = parameter(OID.cidr)
1939
+
1940
+ /**
1941
+ * A `date` parameter, given as `YYYY-MM-DD`, `"infinity"`, or `"-infinity"`.
1942
+ *
1943
+ * @category constructors
1944
+ * @since 4.0.0
1945
+ */
1946
+ export const date: (value: string | null) => Parameter = parameter(OID.date)
1947
+
1948
+ /**
1949
+ * A `time` parameter, given as microseconds since midnight.
1950
+ *
1951
+ * @category constructors
1952
+ * @since 4.0.0
1953
+ */
1954
+ export const time: (value: bigint | null) => Parameter = parameter(OID.time)
1955
+
1956
+ /**
1957
+ * A `timetz` parameter, such as `"12:34:56+02:00"`.
1958
+ *
1959
+ * @category constructors
1960
+ * @since 4.0.0
1961
+ */
1962
+ export const timetz: (value: string | null) => Parameter = parameter(OID.timetz)
1963
+
1964
+ /**
1965
+ * A `timestamp` parameter, given as Unix epoch milliseconds and interpreted
1966
+ * as UTC.
1967
+ *
1968
+ * @category constructors
1969
+ * @since 4.0.0
1970
+ */
1971
+ export const timestamp: (value: number | null) => Parameter = parameter(OID.timestamp)
1972
+
1973
+ /**
1974
+ * A `timestamptz` parameter, given as Unix epoch milliseconds.
1975
+ *
1976
+ * @category constructors
1977
+ * @since 4.0.0
1978
+ */
1979
+ export const timestamptz: (value: number | null) => Parameter = parameter(OID.timestamptz)
1980
+
1981
+ /**
1982
+ * A one-dimensional array parameter whose elements have the given OID.
1983
+ *
1984
+ * @category constructors
1985
+ * @since 4.0.0
1986
+ */
1987
+ export const array = (
1988
+ values: ReadonlyArray<unknown> | null,
1989
+ elementOid: number,
1990
+ registry?: Registry
1991
+ ): Result.Result<Parameter, CodecError> =>
1992
+ result(() => {
1993
+ const arrayOid = arrayOidFor(elementOid, registry)
1994
+ if (arrayOid === undefined) {
1995
+ return fail(`No array type known for element OID ${elementOid}`)
1996
+ }
1997
+ return makeParameter(arrayOid, values)
1998
+ })