@effect/sql-pg 4.0.0-rc.115 → 4.0.0-rc.117
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/README.md +125 -1
- package/ai-docs/package.json +3 -3
- package/dist/PgClient.d.ts +27 -3
- package/dist/PgClient.d.ts.map +1 -1
- package/dist/PgClient.js +1 -0
- package/dist/PgClient.js.map +1 -1
- package/dist/PgConnection.d.ts +54 -14
- package/dist/PgConnection.d.ts.map +1 -1
- package/dist/PgConnection.js +121 -67
- package/dist/PgConnection.js.map +1 -1
- package/dist/PgMigrator.d.ts.map +1 -1
- package/dist/PgMigrator.js +15 -10
- package/dist/PgMigrator.js.map +1 -1
- package/dist/PgPool.d.ts.map +1 -1
- package/dist/PgPool.js +4 -7
- package/dist/PgPool.js.map +1 -1
- package/dist/PgTypes.d.ts +20 -10
- package/dist/PgTypes.d.ts.map +1 -1
- package/dist/PgTypes.js +64 -44
- package/dist/PgTypes.js.map +1 -1
- package/dist/internal/password.d.ts +4 -0
- package/dist/internal/password.d.ts.map +1 -0
- package/dist/internal/password.js +4 -0
- package/dist/internal/password.js.map +1 -0
- package/package.json +5 -5
- package/src/PgClient.ts +29 -4
- package/src/PgConnection.ts +194 -98
- package/src/PgMigrator.ts +18 -19
- package/src/PgPool.ts +4 -7
- package/src/PgTypes.ts +71 -50
- package/src/internal/connection.ts +2 -2
- package/src/internal/password.ts +11 -0
package/src/PgMigrator.ts
CHANGED
|
@@ -13,12 +13,12 @@ import * as Effect from "effect/Effect"
|
|
|
13
13
|
import * as FileSystem from "effect/FileSystem"
|
|
14
14
|
import * as Layer from "effect/Layer"
|
|
15
15
|
import * as Path from "effect/Path"
|
|
16
|
-
import * as Redacted from "effect/Redacted"
|
|
17
16
|
import * as ChildProcess from "effect/unstable/process/ChildProcess"
|
|
18
17
|
import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"
|
|
19
18
|
import * as Migrator from "effect/unstable/sql/Migrator"
|
|
20
19
|
import type { SqlClient } from "effect/unstable/sql/SqlClient"
|
|
21
20
|
import type { SqlError } from "effect/unstable/sql/SqlError"
|
|
21
|
+
import * as Password from "./internal/password.ts"
|
|
22
22
|
import { PgClient } from "./PgClient.ts"
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -45,7 +45,7 @@ export const run: <R2 = never>(
|
|
|
45
45
|
| R2
|
|
46
46
|
> = Migrator.make({
|
|
47
47
|
dumpSchema(path, table) {
|
|
48
|
-
const pgDump = (args: Array<string
|
|
48
|
+
const pgDump = (args: Array<string>, password: string | undefined) =>
|
|
49
49
|
Effect.gen(function*() {
|
|
50
50
|
const sql = yield* PgClient
|
|
51
51
|
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
|
|
@@ -55,9 +55,7 @@ export const run: <R2 = never>(
|
|
|
55
55
|
PGHOST: sql.config.host,
|
|
56
56
|
PGPORT: sql.config.port?.toString(),
|
|
57
57
|
PGUSER: sql.config.username,
|
|
58
|
-
PGPASSWORD:
|
|
59
|
-
? Redacted.value(sql.config.password)
|
|
60
|
-
: undefined,
|
|
58
|
+
PGPASSWORD: password,
|
|
61
59
|
PGDATABASE: sql.config.database,
|
|
62
60
|
PGSSLMODE: sql.config.ssl ? "require" : "prefer"
|
|
63
61
|
}
|
|
@@ -69,21 +67,20 @@ export const run: <R2 = never>(
|
|
|
69
67
|
.replace(/\n{2,}/gm, "\n\n")
|
|
70
68
|
.trim();
|
|
71
69
|
}).pipe(
|
|
72
|
-
Effect.mapError((error) =>
|
|
70
|
+
Effect.mapError((error) =>
|
|
71
|
+
new Migrator.MigrationError({ kind: "Failed", message: error.message, cause: error })
|
|
72
|
+
)
|
|
73
73
|
)
|
|
74
74
|
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
Effect.all([pgDumpSchema, pgDumpMigrations], { concurrency: 2 }),
|
|
85
|
-
([schema, migrations]) => schema + "\n\n" + migrations
|
|
86
|
-
)
|
|
75
|
+
const pgDumpAll = Effect.gen(function*() {
|
|
76
|
+
const sql = yield* PgClient
|
|
77
|
+
const password = yield* Password.resolve(sql.config.password)
|
|
78
|
+
const [schema, migrations] = yield* Effect.all([
|
|
79
|
+
pgDump(["--schema-only"], password),
|
|
80
|
+
pgDump(["--column-inserts", "--data-only", `--table=${table}`], password)
|
|
81
|
+
], { concurrency: 2 })
|
|
82
|
+
return schema + "\n\n" + migrations
|
|
83
|
+
})
|
|
87
84
|
|
|
88
85
|
const pgDumpFile = (path: string) =>
|
|
89
86
|
Effect.gen(function*() {
|
|
@@ -93,7 +90,9 @@ export const run: <R2 = never>(
|
|
|
93
90
|
yield* fs.makeDirectory(path_.dirname(path), { recursive: true })
|
|
94
91
|
yield* fs.writeFileString(path, dump)
|
|
95
92
|
}).pipe(
|
|
96
|
-
Effect.mapError((error) =>
|
|
93
|
+
Effect.mapError((error) =>
|
|
94
|
+
new Migrator.MigrationError({ kind: "Failed", message: error.message, cause: error })
|
|
95
|
+
)
|
|
97
96
|
)
|
|
98
97
|
|
|
99
98
|
return pgDumpFile(path)
|
package/src/PgPool.ts
CHANGED
|
@@ -140,7 +140,7 @@ export const make = Effect.fnUntraced(function*(options: Config): Effect.fn.Retu
|
|
|
140
140
|
Effect.sync(() => {
|
|
141
141
|
createdAt.set(connection, clock.currentTimeMillisUnsafe())
|
|
142
142
|
const internals = connectionInternals(connection)
|
|
143
|
-
internals.
|
|
143
|
+
internals.retireHooks.add(() => {
|
|
144
144
|
deadConnections.add(connection)
|
|
145
145
|
// `deadConnections` is only read by the next checkout, and a checkout
|
|
146
146
|
// already waiting for this connection would never get that far. Tell
|
|
@@ -204,12 +204,9 @@ export const make = Effect.fnUntraced(function*(options: Config): Effect.fn.Retu
|
|
|
204
204
|
// `pin` reserves the pool item itself, so this needs no help.
|
|
205
205
|
const reserve = Effect.flatMap(get, (connection) => connection.pin)
|
|
206
206
|
|
|
207
|
-
// `Pool.use` cannot check
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
// checkout does its replacement pass first. Checking inside the callback
|
|
211
|
-
// instead would hold one lease while acquiring another, which deadlocks a
|
|
212
|
-
// pool of one.
|
|
207
|
+
// `Pool.use` cannot pre-check a session. Use it only when no TTL applies and
|
|
208
|
+
// no connection awaits retirement. Checking inside its callback can deadlock
|
|
209
|
+
// a size-one pool while acquiring a replacement.
|
|
213
210
|
const use = <A, E, R>(
|
|
214
211
|
f: (connection: PgConnection.PgConnection) => Effect.Effect<A, E, R>
|
|
215
212
|
): Effect.Effect<A, E | SqlError, R> =>
|
package/src/PgTypes.ts
CHANGED
|
@@ -10,9 +10,21 @@
|
|
|
10
10
|
* There is no `typeof` inference: an OID is always supplied, either directly
|
|
11
11
|
* or through a constructor such as `int4` that carries it.
|
|
12
12
|
*
|
|
13
|
-
* `timestamp`
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* `timestamp` and `timestamptz` values, including array elements, decode to
|
|
14
|
+
* `Date`. Encoders accept `Date` or epoch milliseconds.
|
|
15
|
+
* Decoding truncates to milliseconds toward zero relative to the PostgreSQL
|
|
16
|
+
* epoch. To restore numeric decoding, override the codecs with `register`
|
|
17
|
+
* or a client `Registry`.
|
|
18
|
+
*
|
|
19
|
+
* `infinity`, `-infinity` and values outside the JavaScript `Date` range
|
|
20
|
+
* (±8.64e15 epoch milliseconds) decode to an invalid `Date`. Numeric
|
|
21
|
+
* `±Infinity` encodes the PostgreSQL sentinels; encoding an invalid `Date` fails.
|
|
22
|
+
*
|
|
23
|
+
* The `timestamp` codec maps wall-clock fields to UTC fields of a `Date`.
|
|
24
|
+
* Date parameters bind as `timestamptz`, so inserting one into a `timestamp`
|
|
25
|
+
* column applies the session `TimeZone`. Use UTC or `timestamp(value)` to
|
|
26
|
+
* preserve its UTC fields. `timestamptz` round trips preserve the instant
|
|
27
|
+
* regardless of session timezone.
|
|
16
28
|
*
|
|
17
29
|
* @since 4.0.0
|
|
18
30
|
*/
|
|
@@ -301,6 +313,7 @@ export const OID = {
|
|
|
301
313
|
timestamptz: 1184,
|
|
302
314
|
timetz: 1266,
|
|
303
315
|
numeric: 1700,
|
|
316
|
+
regclass: 2205,
|
|
304
317
|
uuid: 2950,
|
|
305
318
|
jsonb: 3802,
|
|
306
319
|
boolArray: 1000,
|
|
@@ -324,6 +337,7 @@ export const OID = {
|
|
|
324
337
|
timestamptzArray: 1185,
|
|
325
338
|
timetzArray: 1270,
|
|
326
339
|
numericArray: 1231,
|
|
340
|
+
regclassArray: 2210,
|
|
327
341
|
uuidArray: 2951,
|
|
328
342
|
jsonbArray: 3807
|
|
329
343
|
} as const
|
|
@@ -350,6 +364,7 @@ const arrayToElement = new Map<number, number>([
|
|
|
350
364
|
[OID.timestamptzArray, OID.timestamptz],
|
|
351
365
|
[OID.timetzArray, OID.timetz],
|
|
352
366
|
[OID.numericArray, OID.numeric],
|
|
367
|
+
[OID.regclassArray, OID.regclass],
|
|
353
368
|
[OID.uuidArray, OID.uuid],
|
|
354
369
|
[OID.jsonbArray, OID.jsonb]
|
|
355
370
|
])
|
|
@@ -992,20 +1007,22 @@ const readTimeMicros = (bytes: Uint8Array, offset: number): number => {
|
|
|
992
1007
|
return micros
|
|
993
1008
|
}
|
|
994
1009
|
|
|
995
|
-
/** The two halves of the int64 `timestampInt64` last produced. */
|
|
996
1010
|
let timestampHigh = 0
|
|
997
1011
|
let timestampLow = 0
|
|
998
1012
|
|
|
999
1013
|
/**
|
|
1000
|
-
*
|
|
1001
|
-
* paths read them from here rather than from a returned pair, so neither
|
|
1002
|
-
* allocates.
|
|
1014
|
+
* Writes the wire int64 to timestampHigh/Low, avoiding a pair allocation.
|
|
1003
1015
|
*/
|
|
1004
1016
|
const timestampInt64 = (value: unknown): void => {
|
|
1005
|
-
|
|
1006
|
-
if (
|
|
1007
|
-
|
|
1008
|
-
|
|
1017
|
+
let ms: number
|
|
1018
|
+
if (value instanceof Date) {
|
|
1019
|
+
ms = value.getTime()
|
|
1020
|
+
if (Number.isNaN(ms)) fail("timestamp cannot be an invalid Date")
|
|
1021
|
+
} else {
|
|
1022
|
+
ms = typeof value === "number" ? value : fail("Expected a Date or number for timestamp")
|
|
1023
|
+
if (Number.isNaN(ms)) fail("timestamp cannot be NaN")
|
|
1024
|
+
}
|
|
1025
|
+
if (ms === Number.POSITIVE_INFINITY) {
|
|
1009
1026
|
timestampHigh = INT32_MAX
|
|
1010
1027
|
timestampLow = -1
|
|
1011
1028
|
} else if (ms === Number.NEGATIVE_INFINITY) {
|
|
@@ -1037,17 +1054,14 @@ const timestampCodec: UnsafeCodec<any> = codecOf(
|
|
|
1037
1054
|
requireSize(size, 8, "timestamp")
|
|
1038
1055
|
const high = readInt32(bytes, offset)
|
|
1039
1056
|
if (high >= -MAX_EXACT_HIGH && high < MAX_EXACT_HIGH) {
|
|
1040
|
-
//
|
|
1041
|
-
// allocates no BigInt. Everything outside them, the sentinels included,
|
|
1042
|
-
// needs the exact 64-bit value.
|
|
1057
|
+
// These bounds allow exact conversion without BigInt.
|
|
1043
1058
|
const micros = high * 4294967296 + readUint32(bytes, offset + 4)
|
|
1044
|
-
return (micros - micros % 1000) / 1000 + PG_EPOCH_MS
|
|
1059
|
+
return new Date((micros - micros % 1000) / 1000 + PG_EPOCH_MS)
|
|
1045
1060
|
}
|
|
1046
1061
|
stage8(bytes, offset)
|
|
1047
1062
|
const micros = scratchView8.getBigInt64(0)
|
|
1048
|
-
if (micros === INT64_MAX) return Number.
|
|
1049
|
-
|
|
1050
|
-
return Number(micros / THOUSAND) + PG_EPOCH_MS
|
|
1063
|
+
if (micros === INT64_MAX || micros === INT64_MIN) return new Date(Number.NaN)
|
|
1064
|
+
return new Date(Number(micros / THOUSAND) + PG_EPOCH_MS)
|
|
1051
1065
|
},
|
|
1052
1066
|
(value) => {
|
|
1053
1067
|
timestampInt64(value)
|
|
@@ -1056,7 +1070,6 @@ const timestampCodec: UnsafeCodec<any> = codecOf(
|
|
|
1056
1070
|
writeInt32(bytes, 4, timestampLow)
|
|
1057
1071
|
return bytes
|
|
1058
1072
|
},
|
|
1059
|
-
// Two int32s are the int64, so the sink needs nothing of its own for it.
|
|
1060
1073
|
(sink, value) => {
|
|
1061
1074
|
timestampInt64(value)
|
|
1062
1075
|
sink.int32(timestampHigh)
|
|
@@ -1150,6 +1163,23 @@ const jsonbCodec: UnsafeCodec<any> = codecOf(
|
|
|
1150
1163
|
}
|
|
1151
1164
|
)
|
|
1152
1165
|
|
|
1166
|
+
const oidCodec: UnsafeCodec<any> = codecOf(
|
|
1167
|
+
(bytes, offset, size) => {
|
|
1168
|
+
requireSize(size, 4, "oid")
|
|
1169
|
+
return readUint32(bytes, offset)
|
|
1170
|
+
},
|
|
1171
|
+
(value) => {
|
|
1172
|
+
const num = requireInteger(value, "oid", 0, 4294967295)
|
|
1173
|
+
const bytes = new Uint8Array(4)
|
|
1174
|
+
bytes[0] = num >>> 24
|
|
1175
|
+
bytes[1] = num >>> 16
|
|
1176
|
+
bytes[2] = num >>> 8
|
|
1177
|
+
bytes[3] = num
|
|
1178
|
+
return bytes
|
|
1179
|
+
},
|
|
1180
|
+
(sink, value) => sink.int32(requireInteger(value, "oid", 0, 4294967295))
|
|
1181
|
+
)
|
|
1182
|
+
|
|
1153
1183
|
const builtinScalars = new Map<number, UnsafeCodec<any>>([
|
|
1154
1184
|
[
|
|
1155
1185
|
OID.bool,
|
|
@@ -1214,25 +1244,8 @@ const builtinScalars = new Map<number, UnsafeCodec<any>>([
|
|
|
1214
1244
|
(sink, value) => sink.int32(requireInteger(value, "int4", INT32_MIN, INT32_MAX))
|
|
1215
1245
|
)
|
|
1216
1246
|
],
|
|
1217
|
-
[
|
|
1218
|
-
|
|
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
|
-
],
|
|
1247
|
+
[OID.oid, oidCodec],
|
|
1248
|
+
[OID.regclass, oidCodec],
|
|
1236
1249
|
[
|
|
1237
1250
|
OID.int8,
|
|
1238
1251
|
codecOf(
|
|
@@ -1399,6 +1412,13 @@ const lookupFor = (registry: Registry | undefined): Lookup =>
|
|
|
1399
1412
|
* Registers a binary codec for an OID the built-in catalogue does not cover,
|
|
1400
1413
|
* or overrides a built-in one. Registered codecs take precedence.
|
|
1401
1414
|
*
|
|
1415
|
+
* **Details**
|
|
1416
|
+
*
|
|
1417
|
+
* Unregistered OIDs decode as UTF-8 text. Register binary user-defined types
|
|
1418
|
+
* to avoid garbled output or codec errors, which close the connection when
|
|
1419
|
+
* reading rows. For arrays, use `makeRegistry().register` with
|
|
1420
|
+
* `RegisterOptions.arrayOid` and pass the registry as the client's `types` option.
|
|
1421
|
+
*
|
|
1402
1422
|
* @category registry
|
|
1403
1423
|
* @since 4.0.0
|
|
1404
1424
|
*/
|
|
@@ -1591,9 +1611,9 @@ export interface Column {
|
|
|
1591
1611
|
*
|
|
1592
1612
|
* **Details**
|
|
1593
1613
|
*
|
|
1594
|
-
* Codecs are resolved once per column. SQL `NULL` becomes `null
|
|
1595
|
-
*
|
|
1596
|
-
* fail with `CodecError`.
|
|
1614
|
+
* Codecs are resolved once per column. SQL `NULL` becomes `null`.
|
|
1615
|
+
* Unregistered OIDs decode as UTF-8 text. Invalid UTF-8 and text-format
|
|
1616
|
+
* columns fail with `CodecError`.
|
|
1597
1617
|
*
|
|
1598
1618
|
* **Example** (Updating the reader after `RowDescription`)
|
|
1599
1619
|
*
|
|
@@ -1624,7 +1644,7 @@ export const makeFieldReader = (
|
|
|
1624
1644
|
return (bytes: Uint8Array, offset: number, size: number, column: number): unknown => {
|
|
1625
1645
|
if (size < 0) return null
|
|
1626
1646
|
const codec = codecs[column]
|
|
1627
|
-
if (codec === undefined) return bytes
|
|
1647
|
+
if (codec === undefined) return decodeUtf8(bytes, offset, size)
|
|
1628
1648
|
const read = codec.read
|
|
1629
1649
|
return read === undefined ? codec.decode(bytes.subarray(offset, offset + size)) : read(bytes, offset, size)
|
|
1630
1650
|
}
|
|
@@ -1653,8 +1673,9 @@ export const encode = (value: unknown, oid: number, registry?: Registry): Result
|
|
|
1653
1673
|
*
|
|
1654
1674
|
* **Details**
|
|
1655
1675
|
*
|
|
1656
|
-
*
|
|
1657
|
-
*
|
|
1676
|
+
* Only binary format (`1`) is supported. Unregistered OIDs decode as UTF-8
|
|
1677
|
+
* text; invalid UTF-8 fails with `CodecError`. See `register` for binary
|
|
1678
|
+
* user-defined types, including arrays.
|
|
1658
1679
|
*
|
|
1659
1680
|
* @category decoding
|
|
1660
1681
|
* @since 4.0.0
|
|
@@ -1670,7 +1691,7 @@ export const decode = (
|
|
|
1670
1691
|
return fail(`Only the binary format is supported, received format ${format}`)
|
|
1671
1692
|
}
|
|
1672
1693
|
const codec = lookupFor(registry)(oid)
|
|
1673
|
-
return codec === undefined ? bytes : codec.decode(bytes)
|
|
1694
|
+
return codec === undefined ? decodeUtf8(bytes, 0, bytes.length) : codec.decode(bytes)
|
|
1674
1695
|
})
|
|
1675
1696
|
|
|
1676
1697
|
// -----------------------------------------------------------------------------
|
|
@@ -1962,21 +1983,21 @@ export const time: (value: bigint | null) => Parameter = parameter(OID.time)
|
|
|
1962
1983
|
export const timetz: (value: string | null) => Parameter = parameter(OID.timetz)
|
|
1963
1984
|
|
|
1964
1985
|
/**
|
|
1965
|
-
* A `timestamp` parameter
|
|
1966
|
-
*
|
|
1986
|
+
* A `timestamp` parameter from a `Date` or epoch milliseconds. UTC fields
|
|
1987
|
+
* become the stored wall-clock fields, regardless of session `TimeZone`.
|
|
1967
1988
|
*
|
|
1968
1989
|
* @category constructors
|
|
1969
1990
|
* @since 4.0.0
|
|
1970
1991
|
*/
|
|
1971
|
-
export const timestamp: (value: number | null) => Parameter = parameter(OID.timestamp)
|
|
1992
|
+
export const timestamp: (value: Date | number | null) => Parameter = parameter(OID.timestamp)
|
|
1972
1993
|
|
|
1973
1994
|
/**
|
|
1974
|
-
* A `timestamptz` parameter, given as Unix epoch milliseconds.
|
|
1995
|
+
* A `timestamptz` parameter, given as a `Date` or Unix epoch milliseconds.
|
|
1975
1996
|
*
|
|
1976
1997
|
* @category constructors
|
|
1977
1998
|
* @since 4.0.0
|
|
1978
1999
|
*/
|
|
1979
|
-
export const timestamptz: (value: number | null) => Parameter = parameter(OID.timestamptz)
|
|
2000
|
+
export const timestamptz: (value: Date | number | null) => Parameter = parameter(OID.timestamptz)
|
|
1980
2001
|
|
|
1981
2002
|
/**
|
|
1982
2003
|
* A one-dimensional array parameter whose elements have the given OID.
|
|
@@ -10,8 +10,8 @@ export interface ConnectionInternals {
|
|
|
10
10
|
/** The pool item identity: the unpinned base connection. */
|
|
11
11
|
readonly base: object
|
|
12
12
|
readonly deadError: () => SqlError | undefined
|
|
13
|
-
/** Fired
|
|
14
|
-
readonly
|
|
13
|
+
/** Fired when a pool must stop reusing the connection. */
|
|
14
|
+
readonly retireHooks: Set<() => void>
|
|
15
15
|
/**
|
|
16
16
|
* Installed by a multiplexed pool so that pinning the session also takes it
|
|
17
17
|
* out of shared circulation for as long as the pin lasts. Every route to an
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect"
|
|
2
|
+
import * as Redacted from "effect/Redacted"
|
|
3
|
+
|
|
4
|
+
export const resolve = (
|
|
5
|
+
password: Redacted.Redacted | Effect.Effect<Redacted.Redacted> | undefined
|
|
6
|
+
): Effect.Effect<string | undefined> =>
|
|
7
|
+
password === undefined
|
|
8
|
+
? Effect.succeed(undefined)
|
|
9
|
+
: Effect.isEffect(password)
|
|
10
|
+
? Effect.map(password, Redacted.value)
|
|
11
|
+
: Effect.succeed(Redacted.value(password))
|